GNU/Linux >> Belajar Linux >  >> Linux

Dapatkan nama dan alamat fungsi yang diekspor di linux

Saya sangat kesal ketika melihat pertanyaan yang menanyakan bagaimana melakukan sesuatu di sistem operasi X yang Anda lakukan di Y.

Dalam kebanyakan kasus, ini bukan pendekatan yang berguna, karena setiap sistem operasi (keluarga) cenderung memiliki pendekatan mereka sendiri terhadap masalah, jadi mencoba menerapkan sesuatu yang bekerja di X dalam Y seperti memasukkan kubus ke dalam lubang bundar.

Harap diperhatikan:teks di sini dimaksudkan sebagai kasar, bukan merendahkan; penguasaan bahasa Inggris saya tidak sebaik yang saya inginkan. Ketegasan yang dipadukan dengan bantuan aktual dan petunjuk ke solusi kerja yang diketahui tampaknya bekerja paling baik dalam mengatasi keterbatasan nonteknis, menurut pengalaman saya.

Di Linux, lingkungan pengujian harus gunakan sesuatu seperti

LC_ALL=C LANG=C readelf -s FILE

untuk membuat daftar semua simbol di FILE . readelf adalah bagian dari paket binutils, dan diinstal jika Anda bermaksud membuat binari baru di sistem. Ini mengarah pada kode yang portabel dan kuat. Jangan lupa bahwa Linux mencakup banyak arsitektur perangkat keras yang memiliki perbedaan nyata.

Untuk membuat binari di Linux, Anda biasanya menggunakan beberapa alat yang disediakan di binutils. Jika binutils menyediakan perpustakaan, atau ada perpustakaan ELF berdasarkan kode yang digunakan dalam binutils, akan lebih baik untuk menggunakannya, daripada mengurai output dari utilitas manusia. Namun, tidak ada perpustakaan seperti itu (library libbfd yang digunakan binutils secara internal tidak khusus untuk ELF). Pustaka [URL=http://www.mr511.de/software/english.html]libelf[/URL] bagus, tetapi merupakan karya yang benar-benar terpisah oleh penulis tunggal. Bug di dalamnya telah dilaporkan ke binutils, yang tidak produktif, karena keduanya tidak terkait. Sederhananya, tidak ada jaminan bahwa ia menangani file ELF pada arsitektur tertentu dengan cara yang sama seperti binutils. Oleh karena itu, untuk ketahanan dan keandalan, Anda pasti ingin menggunakan binutils.

Jika Anda memiliki aplikasi pengujian, aplikasi tersebut harus menggunakan skrip, misalkan /usr/lib/yourapp/list-test-functions , untuk mencantumkan fungsi terkait pengujian:

#!/bin/bash
export LC_ALL=C LANG=C
for file in "[email protected]" ; 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

Dengan cara ini, jika ada arsitektur yang memiliki keunikan (dalam readelf binutils format keluaran khususnya), Anda hanya perlu memodifikasi skrip. Memodifikasi skrip sederhana seperti itu tidak sulit, dan mudah untuk memverifikasi bahwa skrip berfungsi dengan benar -- bandingkan saja readelf mentah keluaran ke keluaran skrip; siapa pun bisa melakukannya.

Subrutin yang membuat pipa, fork() s proses anak, mengeksekusi skrip dalam proses anak, dan menggunakan mis. getline() dalam proses induk untuk membaca daftar nama, cukup sederhana dan sangat kuat. Karena ini juga merupakan satu-satunya tempat yang rentan, kami membuatnya sangat mudah untuk memperbaiki kebiasaan atau masalah apa pun di sini dengan menggunakan skrip eksternal tersebut (yang dapat disesuaikan/diperluas untuk menutupi kebiasaan tersebut, dan mudah untuk di-debug). Ingat, jika binutils itu sendiri memiliki bug (selain bug pemformatan keluaran), binari apa pun yang dibuat hampir pasti juga akan menunjukkan bug yang sama.

Menjadi orang yang berorientasi pada Microsoft, Anda mungkin akan kesulitan memahami manfaat dari pendekatan modular semacam itu. (Ini tidak khusus untuk Microsoft, tetapi khusus untuk ekosistem yang dikontrol vendor tunggal di mana pendekatan yang didorong oleh vendor adalah melalui kerangka kerja menyeluruh , dan kotak hitam dengan antarmuka yang bersih namun sangat terbatas. Saya menganggapnya sebagai batasan kerangka kerja, atau taman bertembok yang dipaksakan vendor, atau taman penjara. Terlihat bagus, tapi keluarnya sulit. Untuk deskripsi dan riwayat tentang pendekatan modular yang saya coba jelaskan, lihat misalnya artikel filosofi Unix di Wikipedia.)

Berikut ini menunjukkan bahwa pendekatan Anda juga mungkin dilakukan di Linux -- meskipun kikuk dan rapuh; hal ini dimaksudkan untuk dilakukan dengan menggunakan alat standar sebagai gantinya. Itu bukan pendekatan yang tepat secara umum.

Antarmuka, symbols.h , paling mudah diterapkan menggunakan fungsi callback yang dipanggil untuk setiap simbol yang ditemukan:

#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 */

Pengikatan simbol ELF dan jenis makro khusus untuk ukuran kata, jadi untuk menghindari kerepotan, saya mendeklarasikan jenis enum di atas. Saya menghilangkan beberapa jenis yang tidak menarik (STT_NOTYPE , STT_SECTION , STT_FILE ), namun.

Implementasinya, 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);
}

Saat mengkompilasi di atas, ingatlah untuk menautkan ke dl perpustakaan.

Anda mungkin menemukan gnu_hashtab_symbol_count() fungsi di atas menarik; format tabel tidak didokumentasikan dengan baik di mana pun yang dapat saya temukan. Ini diuji untuk bekerja pada arsitektur i386 dan x86-64, tetapi harus diperiksa terhadap sumber GNU sebelum mengandalkannya dalam kode produksi. Sekali lagi, opsi yang lebih baik adalah menggunakan alat tersebut secara langsung melalui skrip pembantu, karena alat tersebut akan dipasang di mesin pengembangan apa pun.

Secara teknis, sebuah DT_GNU_HASH tabel memberi tahu kita simbol dinamis pertama, dan indeks tertinggi dalam keranjang hash apa pun memberi tahu kita simbol dinamis terakhir, tetapi karena entri dalam DT_SYMTAB tabel simbol selalu dimulai dari 0 (sebenarnya, entri 0 adalah "tidak ada"), saya hanya mempertimbangkan batas atas.

Untuk mencocokkan nama pustaka dan fungsi, saya sarankan menggunakan strncmp() untuk kecocokan awalan untuk perpustakaan (cocokkan di awal nama perpustakaan, hingga . pertama ). Tentu saja, Anda dapat menggunakan fnmatch() jika Anda lebih suka pola glob, atau regcomp()+regexec() jika Anda lebih suka ekspresi reguler (mereka sudah terpasang di pustaka GNU C, tidak diperlukan pustaka eksternal).

Berikut adalah contoh programnya, example.c , yang hanya mencetak semua simbol:

#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;
}

Untuk mengkompilasi dan menjalankan di atas, gunakan misalnya

gcc -Wall -O2 -c symbols.c
gcc -Wall -O2 -c example.c
gcc -Wall -O2 example.o symbols.o -ldl -o example
./example | less

Untuk melihat simbol dalam program itu sendiri, gunakan -rdynamic tandai pada waktu tautan untuk menambahkan semua simbol ke tabel simbol dinamis:

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

Di sistem saya, yang terakhir dicetak

 (): 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

Saya menggunakan ... untuk menandai di mana saya menghapus banyak garis.

Pertanyaan?


Untuk mendapatkan daftar simbol yang diekspor dari pustaka bersama (.so ) di Linux, ada dua cara:yang mudah dan yang sedikit lebih sulit.

Yang mudah adalah menggunakan alat konsol yang sudah tersedia:objdump (termasuk dalam binutils GNU):

$ 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
...

Cara yang sedikit lebih sulit adalah dengan menggunakan libelf dan tulis program C/C++ untuk membuat daftar simbol sendiri. Lihat elfutils paket, yang juga dibangun dari sumber pencemaran nama baik. Ada sebuah program bernama eu-readelf (versi elfutils dari readelf, jangan bingung dengan readelf binutils). eu-readelf -s $LIB daftar simbol yang diekspor menggunakan pencemaran nama baik, jadi Anda harus dapat menggunakannya sebagai titik awal.


Linux
  1. Dapatkan daftar nama fungsi dalam skrip shell

  2. Dapatkan alamat IPv6 di linux menggunakan ioctl

  3. Nama untuk disk ATA dan SATA di Linux

  1. Cara mendapatkan informasi sistem dan perangkat keras di linux

  2. Ganti Nama Semua File dan Nama Direktori menjadi Huruf Kecil di Linux

  3. Cara mendefinisikan dan menggunakan fungsi di Linux Shell Script

  1. Mengapa Anda mendapatkan cp:menghilangkan kesalahan direktori di Linux dan bagaimana mengatasinya

  2. Menggunakan Ctrl-Alt-F6 di Linux, dan tidak bisa mengembalikan layar saya

  3. Bagaimana cara mendapatkan nama dan versi distribusi Linux?