จะลบสัญลักษณ์ C / C ++ ที่ไม่ได้ใช้ด้วย GCC และ ld ได้อย่างไร?


111

ฉันต้องการปรับขนาดของไฟล์ปฏิบัติการของฉันให้เหมาะสมอย่างรุนแรง ( ARMการพัฒนา) และฉันสังเกตเห็นว่าในรูปแบบการสร้างปัจจุบัน ( gcc+ ld) สัญลักษณ์ที่ไม่ได้ใช้ของฉันจะไม่ถูกตัดออก

การใช้งานของarm-strip --strip-unneededสำหรับที่เกิด executables / ห้องสมุดไม่เปลี่ยนขนาดการส่งออกของปฏิบัติการที่(ผมมีความคิดว่าทำไมไม่มีบางทีมันก็ไม่ได้)

จะเป็นอย่างไร(ถ้ามี)ในการแก้ไขไปป์ไลน์อาคารของฉันเพื่อให้สัญลักษณ์ที่ไม่ได้ใช้ถูกดึงออกจากไฟล์ผลลัพธ์


ฉันจะไม่นึกถึงสิ่งนี้ด้วยซ้ำ แต่สภาพแวดล้อมที่ฝังอยู่ในปัจจุบันของฉันไม่ได้ "ทรงพลัง" มากนักและประหยัดได้แม้จะ500Kไม่ได้2Mผลลัพธ์ในการเพิ่มประสิทธิภาพการโหลดที่ดีมาก

อัปเดต:

น่าเสียดายที่gccเวอร์ชันปัจจุบันที่ฉันใช้ไม่มี-dead-stripตัวเลือกและ-ffunction-sections... + --gc-sectionsสำหรับldไม่ได้ให้ความแตกต่างอย่างมีนัยสำคัญสำหรับผลลัพธ์ที่ได้

ฉันตกใจมากที่เรื่องนี้กลายเป็นปัญหาเพราะฉันแน่ใจว่าgcc + ldควรดึงสัญลักษณ์ที่ไม่ได้ใช้ออกโดยอัตโนมัติ (ทำไมพวกเขาถึงต้องเก็บไว้?)


รู้ได้อย่างไรว่าไม่ได้ใช้สัญลักษณ์?
zvrba

ไม่ได้อ้างอิงที่ใดก็ได้ => ไม่ได้ถูกใช้ในแอปพลิเคชันขั้นสุดท้าย ฉันคิดว่าการสร้างกราฟการโทรในขณะที่ comipling / เชื่อมโยงไม่ควรยากมาก
Yippie-Ki-Yay

1
คุณกำลังพยายามลดขนาดของไฟล์. o โดยการลบสัญลักษณ์ที่ตายแล้วหรือคุณกำลังพยายามลดขนาดของรอยรหัสจริงเมื่อโหลดลงในหน่วยความจำที่เรียกใช้งานได้? ความจริงที่คุณพูดว่า "ฝัง" คำใบ้หลัง; คำถามที่คุณถามดูเหมือนจะเน้นไปที่อดีต
Ira Baxter

@Ira ฉันกำลังพยายามลดขนาดเอาต์พุตที่สามารถเรียกใช้งานได้เนื่องจาก(เป็นตัวอย่าง)หากฉันพยายามพอร์ตแอปพลิเคชันที่มีอยู่ซึ่งใช้boostไลบรารี.exeไฟล์ผลลัพธ์จะมีไฟล์อ็อบเจ็กต์ที่ไม่ได้ใช้จำนวนมากและเนื่องจากข้อกำหนดของรันไทม์ที่ฝังอยู่ในปัจจุบันของฉัน การเริ่มต้น10mbแอปพลิเคชันจะใช้เวลานานกว่าตัวอย่างเช่นการเริ่มต้น500kแอปพลิเคชัน
Yippie-Ki-Yay

8
@Yippie: คุณต้องการกำจัดโค้ดเพื่อลดเวลาในการโหลด รหัสที่คุณต้องการกำจัดเป็นวิธีการที่ไม่ได้ใช้งาน / ฯลฯ จากห้องสมุด ใช่คุณต้องสร้างกราฟการโทรเพื่อทำสิ่งนี้ มันไม่ง่ายอย่างนั้น จะต้องเป็นกราฟการโทรทั่วโลกต้องมีความระมัดระวัง (ไม่สามารถลบบางสิ่งที่อาจใช้งานได้) และต้องแม่นยำ (ดังนั้นคุณจึงมีค่าใกล้เคียงกับกราฟการโทรในอุดมคติดังนั้นคุณจึงรู้ว่าอะไรไม่ใช่ ใช้แล้ว). ปัญหาใหญ่คือการสร้างกราฟการโทรทั่วโลกที่แม่นยำ ไม่ทราบว่ามีคอมไพเลอร์จำนวนมากที่ทำเช่นนี้นับประสาลิงก์เกอร์
Ira Baxter

คำตอบ:


131

สำหรับ GCC สามารถทำได้ในสองขั้นตอน:

รวบรวมข้อมูลก่อน แต่บอกให้คอมไพเลอร์แยกโค้ดออกเป็นส่วนต่างๆภายในหน่วยการแปล สิ่งนี้จะทำสำหรับฟังก์ชันคลาสและตัวแปรภายนอกโดยใช้แฟล็กสองคอมไพเลอร์ต่อไปนี้:

-fdata-sections -ffunction-sections

เชื่อมโยงหน่วยการแปลเข้าด้วยกันโดยใช้แฟล็กการเพิ่มประสิทธิภาพตัวเชื่อมโยง (ซึ่งทำให้ตัวเชื่อมโยงทิ้งส่วนที่ไม่ได้อ้างอิง):

-Wl,--gc-sections

ดังนั้นหากคุณมีไฟล์หนึ่งชื่อ test.cpp ที่มีสองฟังก์ชันที่ประกาศอยู่ แต่หนึ่งในนั้นไม่ได้ใช้งานคุณสามารถละเว้นไฟล์ที่ไม่ได้ใช้โดยใช้คำสั่งต่อไปนี้เป็น gcc (g ++):

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

(โปรดทราบว่า -Os เป็นแฟล็กคอมไพเลอร์เพิ่มเติมที่บอกให้ GCC ปรับขนาดให้เหมาะสม)


3
โปรดทราบว่าสิ่งนี้จะทำให้การปฏิบัติการช้าลงตามคำอธิบายตัวเลือกของ GCC (ฉันทดสอบแล้ว)
เปลี่ยนแปลง

1
ด้วยmingwวิธีนี้จะใช้ไม่ได้เมื่อเชื่อมโยง libstdc ++ และ libgcc แบบคงที่แบบคงที่ด้วยแฟล็-staticก ตัวเลือก linker-strip-allช่วยได้ไม่น้อย แต่ยังคงเรียกใช้งานได้ (หรือ dll) ที่สร้างขึ้นนั้นใหญ่กว่า Visual Studio ประมาณ 4 ทาง ประเด็นคือฉันไม่สามารถควบคุมวิธีการlibstdc++คอมไพล์ได้ ควรมีldทางเลือกเดียว
Fabio

34

หากจะเชื่อเธรดนี้คุณจะต้องระบุ-ffunction-sectionsและ-fdata-sectionsไปยัง gcc ซึ่งจะทำให้แต่ละฟังก์ชันและอ็อบเจ็กต์ข้อมูลอยู่ในส่วนของตัวเอง จากนั้นให้และ--gc-sectionsให้ GNU ld เพื่อลบส่วนที่ไม่ได้ใช้ออก


6
@MSalters: ไม่ใช่ค่าเริ่มต้นเนื่องจากละเมิดมาตรฐาน C และ C ++ ทันใดนั้นการเริ่มต้นทั่วโลกก็ไม่เกิดขึ้นซึ่งส่งผลให้โปรแกรมเมอร์บางคนประหลาดใจมาก
Ben Voigt

1
@MSalters: เฉพาะในกรณีที่คุณผ่านตัวเลือกการทำลายพฤติกรรมที่ไม่ได้มาตรฐานซึ่งคุณเสนอให้สร้างพฤติกรรมเริ่มต้น
Ben Voigt

1
@MSalters: หากคุณสามารถสร้างโปรแกรมแก้ไขที่เรียกใช้โปรแกรมเริ่มต้นแบบคงที่ได้ก็ต่อเมื่อผลข้างเคียงที่จำเป็นต่อการทำงานที่ถูกต้องของโปรแกรมนั่นจะยอดเยี่ยมมาก น่าเสียดายที่ฉันคิดว่าการทำอย่างสมบูรณ์แบบมักจะต้องแก้ปัญหาการหยุดชะงักดังนั้นคุณอาจต้องทำผิดพลาดในการรวมสัญลักษณ์พิเศษบางอย่างในบางครั้ง ซึ่งโดยพื้นฐานแล้วก็คือสิ่งที่ไอราพูดในความคิดเห็นของเขาต่อคำถาม (BTW: "ไม่จำเป็นต่อการทำงานที่ถูกต้องของโปรแกรม" เป็นคำจำกัดความของ "ไม่ได้ใช้" ที่แตกต่างไปจากคำที่ใช้ในมาตรฐาน)
Ben Voigt

2
@BenVoigt ใน C การเริ่มต้นทั่วโลกไม่สามารถมีผลข้างเคียง (initializers ต้องเป็นนิพจน์คงที่)
MM

2
@Matt: แต่นั่นไม่เป็นความจริงใน C ++ ... และพวกเขาแชร์ลิงค์เกอร์เดียวกัน
Ben Voigt

25

คุณจะต้องตรวจสอบเอกสารของคุณสำหรับ gcc & ld เวอร์ชันของคุณ:

อย่างไรก็ตามสำหรับฉัน (OS X gcc 4.0.1) ฉันพบสิ่งเหล่านี้สำหรับ ld

-dead_strip

ลบฟังก์ชันและข้อมูลที่ไม่สามารถเข้าถึงได้โดยจุดเข้าใช้งานหรือสัญลักษณ์ที่ส่งออก

-dead_strip_dylibs

ลบ dylib ที่ไม่สามารถเข้าถึงได้โดยจุดเข้าหรือสัญลักษณ์ที่ส่งออก นั่นคือระงับการสร้างคำสั่งคำสั่ง load สำหรับ dylibs ซึ่งไม่มีสัญลักษณ์ใด ๆ ในระหว่างการเชื่อมโยง ไม่ควรใช้ตัวเลือกนี้เมื่อเชื่อมโยงกับ dylib ซึ่งจำเป็นในรันไทม์ด้วยเหตุผลทางอ้อมบางประการเช่น dylib มีตัวเริ่มต้นที่สำคัญ

และตัวเลือกที่เป็นประโยชน์นี้

-why_live symbol_name

บันทึกห่วงโซ่การอ้างอิงไปยัง symbol_name ใช้ได้เฉพาะกับ-dead_strip. สามารถช่วยแก้จุดบกพร่องได้ว่าเหตุใดสิ่งที่คุณคิดว่าควรเป็นแถบที่ตายแล้วจึงไม่ถูกลบออก

นอกจากนี้ยังมีหมายเหตุใน gcc / g ++ man ว่าการกำจัดรหัสตายบางประเภทจะดำเนินการเฉพาะเมื่อเปิดใช้งานการเพิ่มประสิทธิภาพเมื่อคอมไพล์

แม้ว่าตัวเลือก / เงื่อนไขเหล่านี้อาจไม่สามารถใช้ได้กับคอมไพเลอร์ของคุณ แต่ฉันขอแนะนำให้คุณมองหาสิ่งที่คล้ายกันในเอกสารของคุณ


ดูเหมือนว่าจะไม่ทำอะไรmingwเลย
Fabio

-dead_stripไม่ใช่gccตัวเลือก
ar2015

21

นิสัยการเขียนโปรแกรมก็ช่วยได้เช่นกัน เช่นเพิ่มstaticฟังก์ชันที่ไม่สามารถเข้าถึงได้จากภายนอกไฟล์ที่ระบุ ใช้ชื่อที่สั้นกว่าสำหรับสัญลักษณ์ (ช่วยได้เล็กน้อยไม่น่าจะมากเกินไป) ใช้const char x[]เมื่อเป็นไปได้ ... บทความนี้แม้ว่าจะพูดถึงวัตถุที่ใช้ร่วมกันแบบไดนามิก แต่สามารถมีคำแนะนำที่หากปฏิบัติตามสามารถช่วยให้ขนาดเอาต์พุตไบนารีสุดท้ายของคุณเล็กลง (หากเป้าหมายของคุณคือ ELF)


4
การเลือกชื่อที่สั้นลงสำหรับสัญลักษณ์จะช่วยได้อย่างไร
fuz

1
หากสัญลักษณ์ไม่หลุดออกไปça va ก็น่ากลัว - แต่ดูเหมือนว่าตอนนี้จำเป็นต้องพูด
ShinTakezou

@fuz บทความนี้พูดถึงวัตถุที่ใช้ร่วมกันแบบไดนามิก (เช่น.soบน Linux) ดังนั้นชื่อสัญลักษณ์จึงต้องถูกเก็บไว้เพื่อให้ API เช่นctypesโมดูล FFI ของ Python สามารถใช้เพื่อค้นหาสัญลักษณ์ตามชื่อในรันไทม์
ssokolow

18

คำตอบคือ-flto. คุณต้องส่งต่อไปยังขั้นตอนการคอมไพล์และลิงค์มิฉะนั้นจะไม่ทำอะไรเลย

มันใช้งานได้ดีมาก - ลดขนาดโปรแกรมไมโครคอนโทรลเลอร์ที่ฉันเขียนให้น้อยกว่า 50% ของขนาดก่อนหน้า!

น่าเสียดายที่ดูเหมือนว่ามีข้อผิดพลาดเล็กน้อย - ฉันมีบางสิ่งที่สร้างขึ้นไม่ถูกต้อง อาจเป็นเพราะระบบบิลด์ที่ฉันใช้ (QBS มันใหม่มาก) แต่ไม่ว่าในกรณีใดฉันขอแนะนำให้คุณเปิดใช้งานสำหรับบิลด์สุดท้ายของคุณเท่านั้นถ้าเป็นไปได้และทดสอบการสร้างนั้นอย่างละเอียด


1
"-Wl, - gc-section" ใช้ไม่ได้กับ MinGW-W64 "-flto" ใช้ได้กับฉัน ขอบคุณ
rhbc73

การประกอบเอาต์พุตแปลกมาก-fltoเพราะฉันไม่เข้าใจว่ามันทำอะไรอยู่เบื้องหลัง
ar2015

ฉันเชื่อว่า-fltoมันไม่ได้รวบรวมไฟล์แต่ละไฟล์เข้ากับแอสเซมบลีมันจะคอมไพล์เป็น LLVM IR จากนั้นลิงก์สุดท้ายจะรวบรวมไฟล์เหล่านั้นราวกับว่าพวกมันทั้งหมดอยู่ในหน่วยคอมไพล์เดียว นั่นหมายความว่ามันสามารถกำจัดฟังก์ชันที่ไม่ได้ใช้งานและฟังก์ชันที่ไม่ใช่แบบอินไลน์staticและอาจเป็นอย่างอื่นด้วย ดูllvm.org/docs/LinkTimeOptimization.html
Timmmm

13

แม้ว่าจะไม่เกี่ยวกับสัญลักษณ์อย่างเคร่งครัด แต่หากต้องการขนาดให้คอมไพล์ด้วย-Osและ-sแฟล็กเสมอ -Osปรับโค้ดผลลัพธ์ให้เหมาะสมสำหรับขนาดที่สามารถปฏิบัติการได้ขั้นต่ำและ-sลบตารางสัญลักษณ์และข้อมูลการย้ายออกจากไฟล์ปฏิบัติการ

บางครั้ง - หากต้องการขนาดเล็กการเล่นกับแฟล็กการเพิ่มประสิทธิภาพที่แตกต่างกันอาจมีความสำคัญหรือไม่ก็ได้ ตัวอย่างเช่นการสลับ-ffast-mathและ / หรือ-fomit-frame-pointerบางครั้งอาจช่วยคุณประหยัดได้หลายสิบไบต์


การปรับแต่งการเพิ่มประสิทธิภาพส่วนใหญ่จะยังคงให้รหัสที่ถูกต้องตราบเท่าที่คุณปฏิบัติตามมาตรฐานภาษา แต่ฉันได้-ffast-mathสร้างความหายนะในรหัส C ++ ที่เป็นไปตามมาตรฐานอย่างสมบูรณ์ดังนั้นฉันจะไม่แนะนำ
Raptor007

11

สำหรับฉันแล้วดูเหมือนว่าคำตอบที่นีโมให้ไว้นั้นถูกต้อง หากคำแนะนำเหล่านั้นใช้ไม่ได้ผลปัญหาอาจเกี่ยวข้องกับเวอร์ชันของ gcc / ld ที่คุณใช้เนื่องจากแบบฝึกหัดที่ฉันรวบรวมโปรแกรมตัวอย่างโดยใช้คำแนะนำตามรายละเอียดที่นี่

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

จากนั้นฉันรวบรวมรหัสโดยใช้สวิตช์การลบรหัสตายที่ก้าวร้าวมากขึ้นเรื่อย ๆ :

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

พารามิเตอร์การคอมไพล์และการเชื่อมโยงเหล่านี้สร้างไฟล์ปฏิบัติการขนาด 8457, 8164 และ 6160 ไบต์ตามลำดับผลงานที่สำคัญที่สุดมาจากการประกาศ 'สตริป - ออล' หากคุณไม่สามารถทำการลดขนาดที่คล้ายกันบนแพลตฟอร์มของคุณได้แสดงว่า gcc เวอร์ชันของคุณไม่รองรับฟังก์ชันนี้ ฉันใช้ gcc (4.5.2-8ubuntu4), ld (2.21.0.20110327) บน Linux Mint 2.6.38-8-generic x86_64


8

strip --strip-unneededทำงานบนตารางสัญลักษณ์ของไฟล์ปฏิบัติการของคุณเท่านั้น มันไม่ได้ลบโค้ดปฏิบัติการใด ๆ

ไลบรารีมาตรฐานบรรลุผลลัพธ์ที่คุณต้องการโดยการแยกฟังก์ชันทั้งหมดออกเป็นไฟล์อ็อบเจ็กต์แยกซึ่งรวมเข้าด้วยกันโดยใช้ar. หากคุณเชื่อมโยงไฟล์เก็บถาวรผลลัพธ์เป็นไลบรารี (เช่นให้ตัวเลือก-l your_libraryเป็น ld) จากนั้น ld จะรวมเฉพาะไฟล์อ็อบเจ็กต์เท่านั้นดังนั้นสัญลักษณ์ที่ใช้จริง

คุณอาจพบคำตอบบางส่วนสำหรับคำถามการใช้งานที่คล้ายคลึงกันนี้


2
ไฟล์อ็อบเจ็กต์ที่แยกต่างหากในไลบรารีจะเกี่ยวข้องเฉพาะเมื่อทำการลิงก์แบบสแตติก ด้วยไลบรารีที่ใช้ร่วมกันไลบรารีทั้งหมดจะถูกโหลด แต่ไม่รวมอยู่ในไฟล์ปฏิบัติการแน่นอน
Jonathan Leffler

4

ฉันไม่รู้ว่าสิ่งนี้จะช่วยในสถานการณ์ปัจจุบันของคุณได้หรือไม่เนื่องจากนี่เป็นคุณลักษณะล่าสุด แต่คุณสามารถระบุการเปิดเผยสัญลักษณ์ในลักษณะทั่วโลกได้ ผ่าน-fvisibility=hidden -fvisibility-inlines-hiddenคอมไพล์สามารถช่วยให้ตัวเชื่อมโยงกำจัดสัญลักษณ์ที่ไม่จำเป็นได้ในภายหลัง หากคุณกำลังสร้างไฟล์ปฏิบัติการ (ซึ่งตรงข้ามกับไลบรารีที่ใช้ร่วมกัน) ก็ไม่มีอะไรต้องทำอีกแล้ว

ข้อมูลเพิ่มเติม (และวิธีการปรับเม็ดเล็กสำหรับเช่นห้องสมุด) ที่มีอยู่ในวิกิพีเดียจีซี


4

จากคู่มือ GCC 4.2.1 หัวข้อ-fwhole-program:

สมมติว่าหน่วยคอมไพล์ปัจจุบันแสดงถึงโปรแกรมทั้งหมดที่กำลังคอมไพล์ ฟังก์ชันสาธารณะและตัวแปรทั้งหมดยกเว้นmainและสิ่งที่ผสานโดยแอตทริบิวต์externally_visibleจะกลายเป็นฟังก์ชันคงที่และในผลกระทบจะได้รับการปรับให้เหมาะสมมากขึ้นโดยเครื่องมือเพิ่มประสิทธิภาพระหว่างกระบวนการ แม้ว่าอ็อพชันนี้จะเทียบเท่ากับการใช้staticคีย์เวิร์ดอย่างเหมาะสมสำหรับโปรแกรมที่ประกอบด้วยไฟล์เดียวร่วมกับอ็อพชัน--combineแฟล็กนี้สามารถใช้เพื่อคอมไพล์โปรแกรม C ขนาดเล็กส่วนใหญ่เนื่องจากฟังก์ชันและตัวแปรกลายเป็นโลคัลสำหรับหน่วยคอมไพล์รวมทั้งหมดไม่ใช่สำหรับ ซอร์สไฟล์เดียวนั้นเอง


ใช่ แต่น่าจะใช้ไม่ได้กับการรวบรวมแบบเพิ่มหน่วยใด ๆ และอาจจะช้าไปหน่อย
Timmmm

@Timmmm: -fltoฉันสงสัยว่าคุณกำลังความคิดของ
Ben Voigt

ใช่ ฉันพบในภายหลังว่า (ทำไมถึงไม่มีคำตอบ?) น่าเสียดายที่มันดูบักกี้ไปหน่อยดังนั้นฉันขอแนะนำให้ใช้สำหรับการสร้างขั้นสุดท้ายเท่านั้นจากนั้นทดสอบการสร้างจำนวนมาก!
Timmmm

-2

คุณสามารถใช้สตริปไบนารีบนอ็อบเจ็กต์ไฟล์ (เช่นไฟล์ปฏิบัติการ) เพื่อดึงสัญลักษณ์ทั้งหมดออกจากไฟล์

หมายเหตุ: มันเปลี่ยนไฟล์เองและไม่สร้างสำเนา

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.