Bagaimana cara menghapus simbol C / C ++ yang tidak digunakan dengan GCC dan ld?

110

Saya perlu mengoptimalkan ukuran executable saya ( ARMpengembangan) dan saya perhatikan bahwa dalam skema build saya saat ini ( gcc+ ld) simbol yang tidak digunakan tidak dilucuti.

Penggunaan arm-strip --strip-unneededuntuk executable / perpustakaan yang dihasilkan tidak mengubah ukuran keluaran dari executable (saya tidak tahu mengapa, mungkin itu tidak bisa) .

Bagaimana cara (jika ada) untuk memodifikasi pipeline bangunan saya, sehingga simbol yang tidak digunakan dihilangkan dari file yang dihasilkan?


Aku bahkan tidak akan berpikir tentang ini, tapi lingkungan tertanam saya saat ini sangat tidak "kuat" dan menyimpan bahkan 500Kdari 2Mhasil dalam meningkatkan kinerja pemuatan sangat bagus.

Memperbarui:

Sayangnya gccversi saat ini yang saya gunakan tidak memiliki -dead-stripopsi dan -ffunction-sections... + --gc-sectionsuntuk ldtidak memberikan perbedaan yang signifikan untuk keluaran yang dihasilkan.

Saya terkejut bahwa ini bahkan menjadi masalah, karena saya yakin itu gcc + ldharus secara otomatis menghapus simbol yang tidak digunakan (mengapa mereka bahkan harus menyimpannya?).

Yippie-Ki-Yay
sumber
Bagaimana Anda tahu bahwa simbol tidak digunakan?
zvrba
Tidak direferensikan di mana pun => tidak digunakan dalam aplikasi akhir. Saya berasumsi bahwa membangun grafik panggilan saat melakukan comipling / menghubungkan seharusnya tidak terlalu sulit.
Yippie-Ki-Yay
1
Apakah Anda mencoba mengurangi ukuran file .o dengan menghapus simbol mati , atau Anda mencoba mengurangi ukuran jejak kode sebenarnya setelah dimuat ke memori yang dapat dieksekusi? Fakta bahwa Anda mengatakan "tertanam" mengisyaratkan yang terakhir; pertanyaan yang Anda ajukan tampaknya terfokus pada yang pertama.
Ira Baxter
@Ira Saya mencoba untuk mengurangi ukuran output yang dapat dieksekusi, karena (sebagai contoh) jika saya mencoba mem- port beberapa aplikasi yang ada, yang menggunakan boostperpustakaan, .exefile yang dihasilkan berisi banyak file objek yang tidak terpakai dan karena spesifikasi runtime yang saya sematkan saat ini , memulai 10mbaplikasi membutuhkan waktu lebih lama daripada, misalnya, memulai 500kaplikasi.
Yippie-Ki-Yay
8
@ Yippie: Anda ingin membuang kode untuk meminimalkan waktu muat; kode yang ingin Anda hapus adalah metode yang tidak digunakan / etc. dari perpustakaan. Ya, Anda perlu membuat grafik panggilan untuk melakukan ini. Tidak semudah itu; itu harus grafik panggilan global, itu harus konservatif (tidak dapat menghapus sesuatu yang mungkin digunakan) dan harus akurat (sehingga Anda memiliki grafik panggilan yang paling mendekati, sehingga Anda benar-benar tahu apa yang tidak bekas). Masalah besarnya adalah membuat grafik panggilan global yang akurat. Tidak tahu banyak kompiler yang melakukan ini, apalagi linker.
Ira Baxter

Jawaban:

131

Untuk GCC, ini dilakukan dalam dua tahap:

Pertama-tama kompilasi data tetapi beri tahu kompilator untuk memisahkan kode menjadi beberapa bagian terpisah dalam unit terjemahan. Ini akan dilakukan untuk fungsi, kelas, dan variabel eksternal dengan menggunakan dua tanda compiler berikut:

-fdata-sections -ffunction-sections

Tautkan unit terjemahan bersama-sama menggunakan tanda pengoptimalan linker (ini menyebabkan linker membuang bagian yang tidak direferensikan):

-Wl,--gc-sections

Jadi jika Anda memiliki satu file bernama test.cpp yang memiliki dua fungsi yang dideklarasikan di dalamnya, tetapi salah satunya tidak digunakan, Anda dapat menghilangkan yang tidak digunakan dengan perintah berikut ke gcc (g ++):

gcc -Os -fdata-sections -ffunction-sections test.cpp -o test -Wl,--gc-sections

(Perhatikan bahwa -Os adalah tanda compiler tambahan yang memberi tahu GCC untuk mengoptimalkan ukuran)

JT
sumber
3
Harap dicatat ini akan memperlambat eksekusi sesuai deskripsi opsi GCC (saya uji).
Metamorfosis
1
Dengan mingwini tidak berfungsi saat menautkan libstdc ++ dan libgcc secara statis dengan flag -static. Opsi penaut -strip-allsedikit membantu, tetapi masih dapat dieksekusi (atau dll) yang dihasilkan sekitar 4 cara lebih besar dari apa yang akan dihasilkan Visual Studio. Intinya adalah, saya tidak memiliki kendali atas bagaimana libstdc++dikompilasi. Harus ada satu- ldsatunya pilihan.
Fabio
34

Jika utas ini dipercaya, Anda perlu menyediakan -ffunction-sectionsdan-fdata-sections ke gcc, yang akan menempatkan setiap fungsi dan objek data di bagiannya masing-masing. Kemudian Anda memberikan dan --gc-sectionske GNU ld untuk menghapus bagian yang tidak digunakan.

Nemo
sumber
6
@ MSalters: Ini bukan default, karena melanggar standar C dan C ++. Tiba-tiba inisialisasi global tidak terjadi, yang mengakibatkan beberapa programmer sangat terkejut.
Ben Voigt
1
@MSalters: Hanya jika Anda meneruskan opsi penghancur perilaku non-standar, yang Anda usulkan untuk menjadikan perilaku default.
Ben Voigt
1
@ MSalters: Jika Anda dapat membuat tambalan yang menjalankan penginisialisasi statis jika dan hanya jika efek samping diperlukan untuk pengoperasian program yang benar, itu akan luar biasa. Sayangnya saya pikir melakukannya dengan sempurna sering kali membutuhkan penyelesaian masalah terputus-putus, jadi Anda mungkin perlu membuat kesalahan di samping memasukkan beberapa simbol tambahan di kali. Yang pada dasarnya adalah apa yang dikatakan Ira dalam komentarnya atas pertanyaan tersebut. (BTW: "tidak diperlukan untuk pengoperasian program yang benar" adalah definisi yang berbeda dari "tidak digunakan" daripada bagaimana istilah itu digunakan dalam standar)
Ben Voigt
2
@BenVoigt di C, inisialisasi global tidak dapat memiliki efek samping (penginisialisasi harus ekspresi konstan)
MM
2
@Matt: Tapi itu tidak benar di C ++ ... dan mereka berbagi linker yang sama.
Ben Voigt
25

Anda sebaiknya memeriksa dokumen Anda untuk versi gcc & ld:

Namun bagi saya (OS X gcc 4.0.1) saya menemukan ini untuk ld

-dead_strip

Hapus fungsi dan data yang tidak dapat dijangkau oleh titik masuk atau simbol yang diekspor.

-dead_strip_dylibs

Hapus dylib yang tidak dapat dijangkau oleh titik masuk atau simbol yang diekspor. Artinya, menekan pembuatan perintah perintah muat untuk dylib yang tidak memberikan simbol selama penautan. Opsi ini tidak boleh digunakan saat menghubungkan ke dylib yang diperlukan saat runtime untuk beberapa alasan tidak langsung seperti dylib memiliki penginisialisasi penting.

Dan opsi bermanfaat ini

-why_live symbol_name

Mencatat rantai referensi ke symbol_name. Hanya berlaku dengan -dead_strip. Ini dapat membantu men-debug mengapa sesuatu yang menurut Anda harus dihapus strip tidak dihapus.

Ada juga catatan di gcc / g ++ man bahwa penghapusan kode mati jenis tertentu hanya dilakukan jika pengoptimalan diaktifkan saat kompilasi.

Meskipun opsi / ketentuan ini mungkin tidak berlaku untuk kompiler Anda, saya sarankan Anda mencari sesuatu yang serupa di dokumen Anda.

Michael Anderson
sumber
Ini sepertinya tidak ada hubungannya dengan mingw.
Fabio
-dead_stripbukanlah gccpilihan.
ar2015
20

Kebiasaan pemrograman juga bisa membantu; misalnya menambah staticfungsi yang tidak diakses di luar file tertentu; gunakan nama yang lebih pendek untuk simbol (bisa membantu sedikit, kemungkinan tidak terlalu banyak); gunakan const char x[]jika memungkinkan; ... makalah ini , meskipun membahas tentang objek bersama yang dinamis, dapat berisi saran yang, jika diikuti, dapat membantu memperkecil ukuran keluaran biner akhir Anda (jika target Anda adalah ELF).

ShinTakezou
sumber
4
Bagaimana membantu memilih nama yang lebih pendek untuk simbol?
fuz
1
jika simbol tidak dihilangkan, ça va sans dire — tetapi tampaknya itu perlu dikatakan sekarang.
ShinTakezou
@fuz Makalah ini berbicara tentang objek bersama yang dinamis (misalnya .sodi Linux), jadi nama simbol harus dipertahankan sehingga API seperti ctypesmodul FFI Python dapat menggunakannya untuk mencari simbol berdasarkan nama pada waktu proses.
ssokolow
18

Jawabannya adalah -flto. Anda harus meneruskannya ke langkah kompilasi dan tautan Anda, jika tidak maka tidak akan melakukan apa-apa.

Ini sebenarnya bekerja dengan sangat baik - mengurangi ukuran program mikrokontroler yang saya tulis menjadi kurang dari 50% dari ukuran sebelumnya!

Sayangnya itu memang tampak agak buggy - saya punya contoh hal-hal tidak dibangun dengan benar. Mungkin karena sistem build yang saya gunakan (QBS; ini sangat baru), tetapi dalam hal apa pun, saya sarankan Anda hanya mengaktifkannya untuk build akhir Anda jika memungkinkan, dan menguji build itu secara menyeluruh.

Timmmm
sumber
1
"-Wl, - gc-section" tidak berfungsi di MinGW-W64, "-flto" berfungsi untuk saya. Terima kasih
rhbc73
Perakitan keluaran sangat aneh dengan -fltosaya tidak mengerti apa yang dilakukannya di belakang layar.
ar2015
Saya percaya dengan -fltoitu tidak mengkompilasi setiap file untuk dirakit, itu mengkompilasinya ke LLVM IR, dan kemudian tautan terakhir mengkompilasi mereka seolah-olah semuanya berada dalam satu unit kompilasi. Itu berarti dapat menghilangkan fungsi yang tidak terpakai dan inline non- staticone, dan mungkin hal-hal lain juga. Lihat llvm.org/docs/LinkTimeOptimization.html
Timmmm
13

Meskipun tidak hanya tentang simbol, jika mencari ukuran - selalu kompilasi dengan flag -Osdan -s. -Osmengoptimalkan kode yang dihasilkan untuk ukuran minimum yang dapat dieksekusi dan -smenghapus tabel simbol dan informasi relokasi dari yang dapat dieksekusi.

Terkadang - jika ukuran kecil diinginkan - bermain-main dengan flag pengoptimalan yang berbeda mungkin - atau mungkin tidak - memiliki makna. Misalnya toggling -ffast-mathdan / atau -fomit-frame-pointermungkin terkadang menghemat puluhan byte.

zxcdw
sumber
Sebagian besar penyesuaian pengoptimalan masih akan menghasilkan kode yang benar selama Anda mematuhi standar bahasa, tetapi saya telah -ffast-mathmembuat malapetaka dalam kode C ++ yang sepenuhnya sesuai standar, jadi saya tidak akan merekomendasikannya.
Raptor007
11

Menurut saya jawaban yang diberikan oleh Nemo adalah yang benar. Jika instruksi itu tidak berhasil, masalahnya mungkin terkait dengan versi gcc / ld yang Anda gunakan, sebagai latihan saya menyusun program contoh menggunakan instruksi yang dirinci di sini

#include <stdio.h>
void deadcode() { printf("This is d dead codez\n"); }
int main(void) { printf("This is main\n"); return 0 ; }

Kemudian saya menyusun kode menggunakan sakelar penghapusan kode mati yang semakin agresif:

gcc -Os test.c -o test.elf
gcc -Os -fdata-sections -ffunction-sections test.c -o test.elf -Wl,--gc-sections
gcc -Os -fdata-sections -ffunction-sections test.c -o test.elf -Wl,--gc-sections -Wl,--strip-all

Parameter kompilasi dan penautan ini menghasilkan file executable dengan ukuran 8457, 8164 dan 6160 byte, masing-masing, kontribusi paling substansial berasal dari deklarasi 'strip-all'. Jika Anda tidak dapat menghasilkan pengurangan serupa di platform Anda, mungkin versi gcc Anda tidak mendukung fungsi ini. Saya menggunakan gcc (4.5.2-8ubuntu4), ld (2.21.0.20110327) di Linux Mint 2.6.38-8-generic x86_64

Gearoid Murphy
sumber
8

strip --strip-unneededhanya beroperasi pada tabel simbol yang dapat dieksekusi Anda. Itu tidak benar-benar menghapus kode yang dapat dieksekusi.

Pustaka standar mencapai hasil yang Anda cari dengan memisahkan semua fungsinya menjadi file objek terpisah, yang digabungkan menggunakan ar. Jika Anda kemudian menautkan arsip yang dihasilkan sebagai pustaka (mis. Memberikan opsi -l your_libraryke ld) maka ld hanya akan menyertakan file objek, dan oleh karena itu simbol, yang sebenarnya digunakan.

Anda mungkin juga menemukan beberapa jawaban untuk pertanyaan penggunaan yang serupa ini .

Andrew Edgecombe
sumber
2
File objek terpisah di pustaka hanya relevan saat melakukan tautan statis. Dengan pustaka bersama, seluruh pustaka dimuat, tetapi tidak termasuk dalam executable, tentu saja.
Jonathan Leffler
4

Saya tidak tahu apakah ini akan membantu kesulitan Anda saat ini karena ini adalah fitur terbaru, tetapi Anda dapat menentukan visibilitas simbol secara global. Meneruskan -fvisibility=hidden -fvisibility-inlines-hiddenkompilasi dapat membantu penaut untuk menghilangkan simbol yang tidak diperlukan nanti. Jika Anda membuat file yang dapat dieksekusi (sebagai lawan dari pustaka bersama), tidak ada lagi yang bisa dilakukan.

Informasi lebih lanjut (dan pendekatan yang lebih baik untuk misalnya perpustakaan) tersedia di wiki GCC .

Luc Danton
sumber
4

Dari manual GCC 4.2.1, bagian -fwhole-program:

Asumsikan bahwa unit kompilasi saat ini mewakili seluruh program yang sedang dikompilasi. Semua fungsi dan variabel publik dengan pengecualian maindan yang digabungkan oleh atribut externally_visiblemenjadi fungsi statis dan dalam pengaruhnya dioptimalkan secara lebih agresif oleh pengoptimal antarprocedural. Meskipun opsi ini setara dengan penggunaan statickata kunci yang tepat untuk program yang terdiri dari file tunggal, dalam kombinasi dengan opsi --combine, tanda ini dapat digunakan untuk mengkompilasi sebagian besar program C berskala lebih kecil karena fungsi dan variabel menjadi lokal untuk seluruh unit kompilasi gabungan, bukan untuk file sumber tunggal itu sendiri.

awiebe
sumber
Ya, tapi itu mungkin tidak bekerja dengan kompilasi tambahan apa pun dan mungkin akan sedikit lambat.
Timmmm
@Timmmm: Saya kira Anda sedang memikirkan -flto.
Ben Voigt
Iya! Saya kemudian menemukan itu (mengapa tidak ada jawaban?). Sayangnya itu tampak agak buggy, jadi saya hanya merekomendasikannya untuk build terakhir dan kemudian menguji banyak build itu!
Timmmm
-1

Anda dapat menggunakan biner strip pada file objek (mis. Dapat dieksekusi) untuk menghapus semua simbol darinya.

Catatan: itu mengubah file itu sendiri dan tidak membuat salinan.

ton4eg.dll
sumber