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.