ตัวเลือก GCC -fPIC


437

ฉันได้อ่านเกี่ยวกับ ตัวเลือกของ GCC สำหรับอนุสัญญาการสร้างรหัสแต่ไม่เข้าใจว่า "สร้างรหัสตำแหน่งที่ไม่ขึ้นกับตำแหน่ง (PIC)" อย่างไร โปรดยกตัวอย่างเพื่ออธิบายฉันว่ามันหมายความว่าอย่างไร


25
เสียงดังกราวยังใช้ -fPIC
ไปแล้ว

คำตอบ:


526

รหัสตำแหน่งอิสระหมายความว่ารหัสเครื่องที่สร้างขึ้นไม่ได้ขึ้นอยู่กับที่อยู่เฉพาะเพื่อให้ทำงานได้

เช่นการกระโดดจะถูกสร้างขึ้นเป็นญาติมากกว่าแน่นอน

หลอกประกอบ:

PIC: วิธีนี้จะใช้ได้ไม่ว่าจะเป็นรหัสที่อยู่ 100 หรือ 1,000

100: COMPARE REG1, REG2
101: JUMP_IF_EQUAL CURRENT+10
...
111: NOP

Non-PIC: วิธีนี้จะใช้ได้ก็ต่อเมื่อรหัสอยู่ที่ 100

100: COMPARE REG1, REG2
101: JUMP_IF_EQUAL 111
...
111: NOP

แก้ไข: เพื่อตอบสนองต่อความคิดเห็น

หากรหัสของคุณถูกคอมไพล์ด้วย -fPIC มันเหมาะสำหรับการรวมไว้ในห้องสมุด - ห้องสมุดจะต้องสามารถย้ายจากตำแหน่งที่ต้องการในหน่วยความจำไปยังที่อยู่อื่นอาจมีอีกห้องสมุดที่โหลดอยู่แล้วตามที่อยู่ที่ห้องสมุดของคุณต้องการ


36
ตัวอย่างนี้ชัดเจน แต่ในฐานะผู้ใช้ฉันจะสร้างความแตกต่างได้อย่างไรหากฉันสร้างไฟล์ labrary (.so) ที่ใช้ร่วมกันโดยไม่มีตัวเลือก มีบางกรณีที่ไม่มี -fPIC lib ของฉันจะไม่ถูกต้องหรือไม่?
Narek

16
ใช่การสร้างห้องสมุดสาธารณะที่ไม่ใช่ PIC อาจเป็นข้อผิดพลาด
John Zwinck

92
หากต้องการเจาะจงมากขึ้นไลบรารีที่แบ่งใช้ควรถูกแชร์ระหว่างกระบวนการ แต่อาจไม่สามารถโหลดไลบรารีที่อยู่เดียวกันในทั้งสอง หากรหัสไม่ได้ตำแหน่งที่เป็นอิสระจากนั้นแต่ละกระบวนการจะต้องมีการคัดลอกของตัวเอง
Simon Richter

19
@Narek: ข้อผิดพลาดเกิดขึ้นหากกระบวนการหนึ่งต้องการโหลดมากกว่าหนึ่งไลบรารีที่แบ่งใช้ที่ที่อยู่เสมือนเดียวกัน เนื่องจากห้องสมุดไม่สามารถคาดเดาได้ว่าห้องสมุดใดที่สามารถโหลดได้ปัญหานี้จึงไม่สามารถหลีกเลี่ยงได้ด้วยแนวคิดไลบรารีแบบแบ่งใช้แบบเดิม พื้นที่ที่อยู่เสมือนไม่ได้ช่วยที่นี่
Philipp

6
คุณสามารถละเว้น-fPICเมื่อคอมไพล์โปรแกรมหรือไลบรารีแบบสแตติกเนื่องจากมีเพียงหนึ่งโปรแกรมหลักเท่านั้นที่จะมีอยู่ในกระบวนการดังนั้นจึงไม่จำเป็นต้องทำการเปลี่ยนตำแหน่งรันไทม์ ในบางระบบโปรแกรมต่างๆยังคงอยู่ในตำแหน่งที่เป็นอิสระเพื่อความปลอดภัยที่เพิ่มขึ้น
Simon Richter

61

ฉันจะพยายามอธิบายสิ่งที่พูดไปแล้วในวิธีที่ง่ายกว่า

เมื่อใดก็ตามที่มีการโหลด lib ที่ใช้ร่วมกันตัวโหลด (โค้ดบนระบบปฏิบัติการที่โหลดโปรแกรมใด ๆ ที่คุณเรียกใช้) จะเปลี่ยนที่อยู่บางอย่างในรหัสขึ้นอยู่กับว่าวัตถุนั้นโหลดไปที่ใด

ในตัวอย่างข้างต้น "111" ในโค้ดที่ไม่ใช่ PIC จะถูกเขียนโดยโหลดเดอร์ในครั้งแรกที่โหลด

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

สำหรับวัตถุที่ใช้ร่วมกันหากกระบวนการอื่นจะต้องการ "เชื่อมโยง" กับรหัสนั้นเขาจะต้องอ่านมันไปยังที่อยู่เสมือนเดียวกันหรือ "111" จะไม่มีเหตุผล แต่พื้นที่เสมือนนั้นอาจถูกใช้งานแล้วในกระบวนการที่สอง


Whenever a shared lib is loaded, the loader changes some addresses in the code depending on where the object was loaded to.ฉันคิดว่าสิ่งนี้ไม่ถูกต้องหากรวบรวมด้วย -fpic และเหตุผลที่ -fpic มีอยู่สำหรับเหตุผลด้านประสิทธิภาพหรือเนื่องจากคุณมีตัวโหลดที่ไม่สามารถย้ายหรือเพราะคุณต้องการสำเนาหลายชุดในตำแหน่งที่ตั้งอื่นหรือด้วยเหตุผลอื่น ๆ อีกมากมาย
robsn

ทำไมไม่ใช้ -fpic เสมอ
Jay

1
@Jay - เพราะจะต้องใช้การคำนวณอีกหนึ่งครั้ง (ที่อยู่ฟังก์ชัน) สำหรับการเรียกใช้ฟังก์ชันแต่ละครั้ง ดังนั้นประสิทธิภาพฉลาดถ้าไม่ต้องการมันจะดีกว่าที่จะไม่ใช้มัน
Roee Gavirel

45

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


เหตุใดจึงไม่โหลดไลบรารีที่ใช้ร่วมกันตามที่อยู่ใด ๆ ในหน่วยความจำโดยไม่-fPICตั้งค่าสถานะไว้ มันไม่ได้เชื่อมโยงกับโปรแกรมหรือไม่ เมื่อโปรแกรมทำงานระบบปฏิบัติการจะอัปโหลดไปยังหน่วยความจำ ฉันพลาดอะไรไปรึเปล่า?
Tony Tannous

1
มีการ-fPICใช้แฟล็กเพื่อให้แน่ใจว่า lib นี้สามารถโหลดไปยังที่อยู่เสมือนใด ๆในกระบวนการที่เชื่อมโยงหรือไม่ ขออภัยสำหรับความคิดเห็นสองครั้งที่ผ่านไป 5 นาทีไม่สามารถแก้ไขได้ก่อนหน้านี้
Tony Tannous

1
แยกแยะระหว่างการสร้างไลบรารีที่แบ่งใช้ (การสร้างlibwotnot.so) และการเชื่อมโยงกับมัน ( -lwotnot) -fPICในขณะที่การเชื่อมโยงที่คุณไม่จำเป็นต้องเอะอะเกี่ยวกับ มันเคยเป็นกรณีที่เมื่อสร้างห้องสมุดที่ใช้ร่วมกันคุณจำเป็นต้องตรวจสอบให้แน่ใจว่า-fPICมีการใช้ไฟล์วัตถุทั้งหมดที่จะสร้างไว้ในห้องสมุดที่ใช้ร่วมกัน กฎอาจมีการเปลี่ยนแปลงเนื่องจากคอมไพเลอร์สร้างด้วยรหัส PIC โดยค่าเริ่มต้นวันนี้ ดังนั้นสิ่งที่สำคัญเมื่อ 20 ปีก่อนและอาจมีความสำคัญเมื่อ 7 ปีที่แล้วมีความสำคัญน้อยกว่าในทุกวันนี้ ที่อยู่นอกเคอร์เนล o / s คือ 'ที่อยู่เสมือน' เสมอ '
Jonathan Leffler

-fPICดังนั้นก่อนหน้านี้คุณมีการเพิ่ม หากไม่ผ่านการตั้งค่าสถานะนี้รหัสที่สร้างขึ้นเมื่อสร้าง. so จำเป็นต้องโหลดไปยังที่อยู่เสมือนเฉพาะที่อาจมีการใช้งานหรือไม่
Tony Tannous

1
ใช่เพราะถ้าคุณไม่ได้ใช้การตั้งค่าสถานะ PIC รหัสไม่สามารถ relocatable ได้อย่างน่าเชื่อถือ สิ่งต่าง ๆ เช่น ASLR (การสุ่มเลย์เอาต์พื้นที่ที่อยู่) เป็นไปไม่ได้หากรหัสไม่ใช่ PIC (หรืออย่างน้อยก็ยากที่จะบรรลุว่าเป็นไปไม่ได้อย่างมีประสิทธิภาพ)
Jonathan Leffler

21

กำลังเพิ่มเพิ่มเติม ...

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

ดังนั้นหากหนึ่ง exe ที่ไม่มีการเชื่อมโยงที่ใช้ร่วมกัน (สถานการณ์สมมุติ) เราสามารถให้ที่อยู่เสมือนเดียวกันกับคำสั่ง asm เดียวกันโดยไม่มีอันตรายใด ๆ

แต่เมื่อเราต้องการที่จะเชื่อมโยงวัตถุที่ใช้ร่วมกันกับ exe แล้วเราไม่แน่ใจว่าที่อยู่เริ่มต้นที่กำหนดให้กับวัตถุที่ใช้ร่วมกันมันจะขึ้นอยู่กับการสั่งซื้อวัตถุที่ใช้ร่วมกันถูกเชื่อมโยงที่กล่าวว่าการสอน asm ภายใน. ที่อยู่เสมือนที่แตกต่างกันขึ้นอยู่กับกระบวนการเชื่อมโยงไปยัง

ดังนั้นกระบวนการหนึ่งสามารถให้ที่อยู่เริ่มต้นเป็น. so ในฐานะที่เป็น 0x45678910 ในพื้นที่เสมือนของตัวเองและกระบวนการอื่น ๆ ในเวลาเดียวกันสามารถให้ที่อยู่เริ่มต้นของ 0x12131415 และถ้าพวกเขาไม่ใช้การกำหนดที่อยู่ญาติดังนั้นจะไม่ทำงานเลย

ดังนั้นพวกเขาจะต้องใช้โหมดที่อยู่ญาติและตัวเลือก fpic


1
ขอบคุณสำหรับคำอธิบายเสมือนของ addr
Hot.PxL

2
ทุกคนสามารถอธิบายได้ว่านี่ไม่ใช่ปัญหากับห้องสมุดแบบคงที่ทำไมคุณไม่ต้องใช้ -fPIC ในห้องสมุดแบบคงที่? ฉันเข้าใจว่าการเชื่อมโยงเสร็จสิ้นในเวลารวบรวม (หรือหลังจากจริง ๆ ) แต่ถ้าคุณมี 2 สแตติกไลบรารีที่มีโค้ดที่ขึ้นกับตำแหน่งแล้วจะเชื่อมโยงกันอย่างไร?
Michael P

3
@MichaelP อ็อบเจ็กต์ไฟล์มีตารางตำแหน่งขึ้นอยู่กับเลเบลและเมื่อไฟล์ obj เฉพาะถูกเชื่อมโยงเลเบลทั้งหมดจะถูกอัพเดตตามลำดับ สิ่งนี้ไม่สามารถทำได้ในห้องสมุดสาธารณะ
สลาวา

16

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


มีสองวิธีที่ใช้กันทั่วไปในการจัดการกับปัญหานี้:

1.Relocation ตัวชี้และที่อยู่ทั้งหมดในรหัสได้รับการแก้ไขหากจำเป็นเพื่อให้พอดีกับที่อยู่โหลดจริง การย้ายใหม่ทำได้โดยตัวเชื่อมโยงและตัวโหลด

รหัส 2.Position อิสระ ที่อยู่ทั้งหมดในรหัสนั้นสัมพันธ์กับตำแหน่งปัจจุบัน อ็อบเจ็กต์ที่แบ่งใช้ในระบบที่คล้าย Unix จะใช้โค้ดที่ไม่ขึ้นกับตำแหน่งโดยค่าเริ่มต้น สิ่งนี้มีประสิทธิภาพน้อยกว่าการเปลี่ยนตำแหน่งหากโปรแกรมทำงานเป็นเวลานานโดยเฉพาะในโหมด 32 บิต


ชื่อ " รหัสตำแหน่งอิสระ " จริง ๆ แล้วหมายถึงต่อไปนี้:

  • ส่วนรหัสไม่มีที่อยู่แบบสัมบูรณ์ที่ต้องการการย้ายใหม่ แต่มีเพียงที่อยู่แบบสัมพันธ์เท่านั้น ดังนั้นส่วนรหัสสามารถโหลดได้ที่ที่อยู่หน่วยความจำโดยพลการและใช้ร่วมกันระหว่างกระบวนการหลายกระบวนการ

  • ส่วนข้อมูลไม่ถูกแชร์ระหว่างหลายกระบวนการเพราะมักจะมีข้อมูลที่เขียนได้ ดังนั้นส่วนข้อมูลอาจมีตัวชี้หรือที่อยู่ที่ต้องการย้ายที่อยู่

  • ฟังก์ชั่นสาธารณะและข้อมูลสาธารณะทั้งหมดสามารถแทนที่ได้ใน Linux หากฟังก์ชั่นในการปฏิบัติการหลักมีชื่อเดียวกับฟังก์ชั่นในวัตถุที่ใช้ร่วมกันแล้วรุ่นในหลักจะมีความสำคัญไม่เพียง แต่เมื่อถูกเรียกจากหลัก แต่ยังเมื่อถูกเรียกจากวัตถุที่ใช้ร่วมกัน ในทำนองเดียวกันเมื่อตัวแปรส่วนกลางใน main มีชื่อเดียวกันกับตัวแปรกลางในวัตถุที่ใช้ร่วมกันแล้วจะใช้อินสแตนซ์ใน main แม้ว่าจะเข้าถึงได้จากวัตถุที่ใช้ร่วมกัน


การรบกวนสัญลักษณ์ที่เรียกว่านี้มีวัตถุประสงค์เพื่อเลียนแบบพฤติกรรมของห้องสมุดคงที่

วัตถุที่ใช้ร่วมกันมีตารางของตัวชี้ไปยังฟังก์ชั่นของมันเรียกว่าขั้นตอนการเชื่อมโยงตาราง (PLT) และตารางของตัวชี้ไปยังตัวแปรที่เรียกว่าตารางการชดเชยทั่วโลก (GOT) เพื่อใช้คุณสมบัติ "แทนที่" การเข้าถึงฟังก์ชั่นและตัวแปรสาธารณะทั้งหมดผ่านตารางนี้

ps ในกรณีที่ไม่สามารถหลีกเลี่ยงการเชื่อมโยงแบบไดนามิกได้มีหลายวิธีในการหลีกเลี่ยงคุณสมบัติการระบุเวลาของรหัสตำแหน่งที่เป็นอิสระ

คุณสามารถอ่านเพิ่มเติมได้จากบทความนี้: http://www.agner.org/optimize/optimizing_cpp.pdf


9

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

รายการเหล่านี้อนุญาตให้โหลดเดอร์ (บิตของรหัสที่โหลดโปรแกรมลงในหน่วยความจำ) เพื่อเขียนที่อยู่สัมบูรณ์เพื่อปรับเปลี่ยนสำหรับที่อยู่โหลดจริงในพื้นที่ที่อยู่เสมือน

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

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

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

เนื่องจากการโทรเข้าสู่ไลบรารีเหล่านี้จากเนื้อความของโปรแกรมหลักจะทำการเปลี่ยนตำแหน่งใหม่ซึ่งทำให้มีโอกาสน้อยกว่าที่จะต้องคัดลอกไลบรารีที่ใช้ร่วมกัน

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