लिंकर क्या करते हैं?


127

मुझे हमेशा आश्चर्य होता है। मुझे पता है कि कंपाइलर आपके द्वारा लिखे गए कोड को बायनेरिज़ में बदल देते हैं लेकिन लिंकर क्या करते हैं? वे हमेशा मेरे लिए एक रहस्य रहे हैं।

मैं मोटे तौर पर समझता हूं कि 'लिंकिंग' क्या है। यह तब होता है जब पुस्तकालयों और रूपरेखा के संदर्भ द्विआधारी में जोड़े जाते हैं। मुझे इससे परे कुछ भी समझ में नहीं आता है। मेरे लिए यह "बस काम करता है"। मैं डायनेमिक लिंकिंग की मूल बातें भी समझता हूं लेकिन बहुत गहरी बात नहीं।

क्या कोई शर्तों की व्याख्या कर सकता है?

जवाबों:


160

लिंकर्स को समझने के लिए, यह समझने में मदद करता है कि "हूड के नीचे" क्या होता है जब आप एक स्रोत फ़ाइल (जैसे कि सी या सी ++ फ़ाइल) को एक निष्पादन योग्य फ़ाइल में परिवर्तित करते हैं (एक निष्पादन योग्य फ़ाइल एक फाइल है जिसे आपके मशीन पर निष्पादित किया जा सकता है) किसी और की मशीन उसी मशीन की वास्तुकला को चला रही है)।

हुड के तहत, जब एक प्रोग्राम संकलित किया जाता है, तो कंपाइलर स्रोत फ़ाइल को ऑब्जेक्ट बाइट कोड में परिवर्तित करता है। यह बाइट कोड (जिसे कभी-कभी ऑब्जेक्ट कोड कहा जाता है) मेंमोनोनिक निर्देश हैं जो केवल आपके कंप्यूटर आर्किटेक्चर को समझते हैं। परंपरागत रूप से, इन फ़ाइलों में एक .OBJ एक्सटेंशन होता है।

ऑब्जेक्ट फ़ाइल बनाने के बाद, लिंकर खेल में आता है। अधिक बार नहीं, एक वास्तविक कार्यक्रम जो कुछ भी उपयोगी होता है उसे अन्य फ़ाइलों को संदर्भित करने की आवश्यकता होगी। सी में, उदाहरण के लिए, स्क्रीन पर अपना नाम प्रिंट करने के लिए एक साधारण कार्यक्रम शामिल होगा:

printf("Hello Kristina!\n");

जब कंपाइलर ने आपके प्रोग्राम को ओब्ज फाइल में संकलित किया, तो यह केवल printfफ़ंक्शन का संदर्भ देता है। लिंकर इस संदर्भ को हल करता है। अधिकांश प्रोग्रामिंग भाषाओं में उस भाषा से अपेक्षित मूल सामान को कवर करने के लिए दिनचर्या का एक मानक पुस्तकालय है। लिंकर आपकी OBJ फाइल को इस मानक पुस्तकालय से जोड़ता है। लिंकर आपकी OBJ फाइल को अन्य OBJ फाइलों के साथ भी लिंक कर सकता है। आप अन्य OBJ फाइलें बना सकते हैं जिनमें ऐसे कार्य हैं जो किसी अन्य OBJ फाइल द्वारा कहे जा सकते हैं। लिंकर लगभग एक वर्ड प्रोसेसर की कॉपी और पेस्ट की तरह काम करता है। यह उन सभी आवश्यक कार्यों को "कॉपी" करता है जो आपके प्रोग्राम को संदर्भित करता है और एक एकल निष्पादन योग्य बनाता है। कभी-कभी कॉपी किए गए अन्य पुस्तकालय अभी तक अन्य OBJ या लाइब्रेरी फ़ाइलों पर निर्भर होते हैं। कभी-कभी एक लिंकर को अपना काम करने के लिए बहुत पुनरावर्ती प्राप्त करना पड़ता है।

ध्यान दें कि सभी ऑपरेटिंग सिस्टम एक एकल निष्पादन योग्य नहीं बनाते हैं। उदाहरण के लिए, Windows, DLL का उपयोग करता है जो इन सभी कार्यों को एक फ़ाइल में एक साथ रखता है। यह आपके निष्पादन योग्य के आकार को कम करता है, लेकिन आपके निष्पादन योग्य को इन विशिष्ट DLL पर निर्भर करता है। DOS ओवरले (.OVL फ़ाइलों) नामक चीजों का उपयोग करता था। इसके कई उद्देश्य थे, लेकिन एक था आमतौर पर उपयोग किए जाने वाले कार्यों को 1 फ़ाइल में रखना (दूसरा उद्देश्य जो वह सेवा कर रहा है, यदि आप सोच रहे हैं, तो बड़े कार्यक्रमों को मेमोरी में फिट करने में सक्षम होना चाहिए। डॉस की मेमोरी और ओवरले में सीमा हो सकती है। स्मृति से "अनलोड" होना और अन्य ओवरले उस मेमोरी के शीर्ष पर "लोड" हो सकते हैं, इसलिए नाम, "ओवरले")। लिनक्स ने पुस्तकालयों को साझा किया है, जो मूल रूप से डीएलएल के समान विचार है (हार्ड कोर लिनक्स लोग जो मुझे पता है कि मुझे बताएंगे कि कितने बड़े अंतर हैं)।

आशा है कि यह आपको समझने में मदद करेगा!


9
बहुत बढ़िया जवाब। इसके अतिरिक्त अधिकांश आधुनिक लिंकर्स टेम्पलेट इंस्टेंसेस जैसे अनावश्यक कोड को हटा देंगे।
एडवर्ड स्ट्रेंज

1
क्या यह उन मतभेदों में से कुछ पर जाने के लिए एक उपयुक्त जगह है?
जॉन पी।

2
नमस्ते, मान लीजिए कि मेरी फ़ाइल किसी अन्य फ़ाइल का संदर्भ नहीं देती है। मान लीजिए, मैं केवल दो चर घोषित करता हूं और आरंभ करता हूं। क्या यह स्रोत फ़ाइल भी लिंकर में जाएगी?
मंगेश खेरडेकर

3
@MangeshKherdekar - हाँ, यह हमेशा एक लिंकर के माध्यम से जाता है। लिंकर किसी भी बाहरी पुस्तकालयों को लिंक नहीं कर सकता है, लेकिन लिंकिंग चरण अभी भी एक निष्पादन योग्य उत्पादन करने के लिए होता है।
आइस्क्रीमिंड

78

पता स्थानांतरण न्यूनतम उदाहरण

पता स्थानांतरण लिंकिंग के महत्वपूर्ण कार्यों में से एक है।

तो आइए इस पर एक नज़र डालें कि यह न्यूनतम उदाहरण के साथ कैसे काम करता है।

०) परिचय

सारांश: स्थानांतरण .textअनुवाद करने के लिए ऑब्जेक्ट फ़ाइलों के अनुभाग को संपादित करता है :

  • ऑब्जेक्ट फ़ाइल पता
  • निष्पादन योग्य के अंतिम पते में

यह लिंकर द्वारा किया जाना चाहिए क्योंकि कंपाइलर एक समय में केवल एक इनपुट फ़ाइल देखता है, लेकिन हमें यह जानने के लिए कि एक ही बार में सभी ऑब्जेक्ट फ़ाइलों के बारे में पता होना चाहिए:

  • घोषित अपरिभाषित कार्यों की तरह अपरिभाषित प्रतीकों को हल करें
  • कई टकराव नहीं .textहै और .dataकई वस्तु फ़ाइलों के वर्गों

आवश्यक शर्तें: न्यूनतम समझ:

  • x86-64 या IA-32 विधानसभा
  • एक ELF फ़ाइल की वैश्विक संरचना। मैंने उसके लिए एक ट्यूटोरियल बनाया है

लिंकिंग का विशेष रूप से C या C ++ से कोई लेना-देना नहीं है: कंपाइलर केवल ऑब्जेक्ट फ़ाइलों को उत्पन्न करते हैं। लिंकर फिर उन्हें इनपुट के रूप में लेता है, बिना यह जाने कि उन्हें किस भाषा ने संकलित किया है। यह फोरट्रान भी हो सकता है।

तो क्रस्ट को कम करने के लिए, आइए एक NASM x86-64 ELF लिनक्स हेलो दुनिया का अध्ययन करें:

section .data
    hello_world db "Hello world!", 10
section .text
    global _start
    _start:

        ; sys_write
        mov rax, 1
        mov rdi, 1
        mov rsi, hello_world
        mov rdx, 13
        syscall

        ; sys_exit
        mov rax, 60
        mov rdi, 0
        syscall

संकलित और इसके साथ इकट्ठा:

nasm -o hello_world.o hello_world.asm
ld -o hello_world.out hello_world.o

NASM 2.10.09 के साथ।

१) .टेक्स्ट ऑफ़ .ओ

सबसे पहले हम .textऑब्जेक्ट फाइल के सेक्शन को डिकंपाइल करते हैं :

objdump -d hello_world.o

जो देता है:

0000000000000000 <_start>:
   0:   b8 01 00 00 00          mov    $0x1,%eax
   5:   bf 01 00 00 00          mov    $0x1,%edi
   a:   48 be 00 00 00 00 00    movabs $0x0,%rsi
  11:   00 00 00
  14:   ba 0d 00 00 00          mov    $0xd,%edx
  19:   0f 05                   syscall
  1b:   b8 3c 00 00 00          mov    $0x3c,%eax
  20:   bf 00 00 00 00          mov    $0x0,%edi
  25:   0f 05                   syscall

महत्वपूर्ण रेखाएँ हैं:

   a:   48 be 00 00 00 00 00    movabs $0x0,%rsi
  11:   00 00 00

जो rsiरजिस्टर में हैलो वर्ल्ड स्ट्रिंग के पते को स्थानांतरित करना चाहिए , जो कि लेखन सिस्टम कॉल को पास किया गया है।

लेकिन रुकें! कंपाइलर संभवतः यह जान सकता है कि "Hello world!"प्रोग्राम लोड होने पर मेमोरी में कहां समाप्त होगा?

खैर, यह नहीं हो सकता है, विशेष रूप से जब हम .oकई .dataखंडों के साथ फ़ाइलों का एक गुच्छा लिंक करते हैं।

केवल लिंकर ही ऐसा कर सकता है क्योंकि केवल उसके पास ही उन सभी ऑब्जेक्ट फ़ाइलों में होगा।

तो संकलक सिर्फ:

  • 0x0संकलित आउटपुट पर एक प्लेसहोल्डर मान डालता है
  • अच्छे पते के साथ संकलित कोड को संशोधित करने के तरीके के लिंक के लिए कुछ अतिरिक्त जानकारी देता है

यह "अतिरिक्त जानकारी" .rela.textऑब्जेक्ट फ़ाइल के अनुभाग में निहित है

2) .rela.text

.rela.text ".text अनुभाग के स्थानांतरण" के लिए खड़ा है।

स्थानांतरण शब्द का उपयोग किया जाता है क्योंकि लिंक करने वाले को ऑब्जेक्ट से निष्पादन योग्य में पता स्थानांतरित करना होगा।

हम इस .rela.textअनुभाग को अलग कर सकते हैं :

readelf -r hello_world.o

जिसमें है;

Relocation section '.rela.text' at offset 0x340 contains 1 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
00000000000c  000200000001 R_X86_64_64       0000000000000000 .data + 0

इस सेक्शन का प्रारूप तय किया गया है: http://www.sco.com/developers/gabi/2003-12-17/ch4.relp.html

प्रत्येक प्रविष्टि लिंकर को एक पते के बारे में बताती है जिसे स्थानांतरित करने की आवश्यकता होती है, यहां हमारे पास स्ट्रिंग के लिए केवल एक है।

थोड़ा सा सरलीकरण, इस विशेष पंक्ति के लिए हमारे पास निम्नलिखित जानकारी है:

  • Offset = C: .textइस प्रविष्टि के पहले बाइट में क्या बदलाव होता है।

    यदि हम विघटित पाठ को वापस देखते हैं, तो यह वास्तव में महत्वपूर्ण के अंदर है movabs $0x0,%rsi, और जो x86-64 निर्देश एन्कोडिंग को जानते हैं वे ध्यान देंगे कि यह निर्देश का 64-बिट पता भाग को एन्कोड करता है।

  • Name = .data: पता .dataअनुभाग को इंगित करता है

  • Type = R_X86_64_64, जो निर्दिष्ट करता है कि पते का अनुवाद करने के लिए वास्तव में क्या गणना की जानी है।

    यह क्षेत्र वास्तव में प्रोसेसर पर निर्भर है, और इस प्रकार AMD64 सिस्टम V ABI एक्सटेंशन पर प्रलेखित है खंड 4.4 "पुनर्वास" ।

    यह दस्तावेज़ कहता है कि R_X86_64_64यह करता है:

    • Field = word64: 8 बाइट्स, इस प्रकार 00 00 00 00 00 00 00 00पते पर0xC

    • Calculation = S + A

      • Sहै मूल्य पते पर दूसरी जगह किया जा रहा है, इस प्रकार00 00 00 00 00 00 00 00
      • Aजो 0यहाँ है परिशिष्ट है । यह स्थानांतरण प्रविष्टि का एक क्षेत्र है।

      तो S + A == 0और हम .dataखंड के पहले पते पर स्थानांतरित हो जाएंगे ।

3) .Txt के बाहर

अब ldहमारे लिए तैयार निष्पादन योग्य के पाठ क्षेत्र को देखें :

objdump -d hello_world.out

देता है:

00000000004000b0 <_start>:
  4000b0:   b8 01 00 00 00          mov    $0x1,%eax
  4000b5:   bf 01 00 00 00          mov    $0x1,%edi
  4000ba:   48 be d8 00 60 00 00    movabs $0x6000d8,%rsi
  4000c1:   00 00 00
  4000c4:   ba 0d 00 00 00          mov    $0xd,%edx
  4000c9:   0f 05                   syscall
  4000cb:   b8 3c 00 00 00          mov    $0x3c,%eax
  4000d0:   bf 00 00 00 00          mov    $0x0,%edi
  4000d5:   0f 05                   syscall

तो केवल एक चीज जो ऑब्जेक्ट फ़ाइल से बदल जाती है, वह है महत्वपूर्ण लाइनें:

  4000ba:   48 be d8 00 60 00 00    movabs $0x6000d8,%rsi
  4000c1:   00 00 00

जो अब पते 0x6000d8( d8 00 60 00 00 00 00 00छोटे-से-अंत में) के बजाय इंगित करता है0x0

क्या यह सही स्थान है hello_world स्ट्रिंग के ?

यह तय करने के लिए हमें प्रोग्राम हेडर की जांच करनी होगी, जो लिनक्स को बताएगा कि प्रत्येक सेक्शन को कहां लोड करना है।

हम उन्हें अलग करते हैं:

readelf -l hello_world.out

जो देता है:

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x00000000000000d7 0x00000000000000d7  R E    200000
  LOAD           0x00000000000000d8 0x00000000006000d8 0x00000000006000d8
                 0x000000000000000d 0x000000000000000d  RW     200000

 Section to Segment mapping:
  Segment Sections...
   00     .text
   01     .data

यह हमें बताता है कि .dataअनुभाग, जो दूसरा है, VirtAddr= पर शुरू होता है0x06000d8

और डेटा अनुभाग पर केवल एक चीज हमारी हैलो वर्ल्ड स्ट्रिंग है।

बोनस स्तर


1
यार तुम कमाल हो। 'ईएलएफ फ़ाइल की वैश्विक संरचना' ट्यूटोरियल का लिंक टूट गया है।
एडम ज़हरान

1
@ अदमझरन धन्यवाद! बेवकूफ GitHub URLs जो स्लैश से निपट नहीं सकते हैं!
सिरो सेंटिल्ली 郝海东 冠状 iro i 法轮功 ''

15

'सी' जैसी भाषाओं में, कोड के अलग-अलग मॉड्यूल को पारंपरिक रूप से ऑब्जेक्ट कोड के ब्लॉब्स में अलग-अलग संकलित किया जाता है, जो कि हर संदर्भ में निष्पादित करने के लिए तैयार है, इसके अलावा सभी संदर्भ जो मॉड्यूल खुद के बाहर बनाता है (यानी पुस्तकालयों या अन्य मॉड्यूल के लिए) अभी तक हल नहीं किया गया है (यानी वे खाली हैं, किसी के साथ आने और सभी कनेक्शन बनाने के लिए लंबित हैं)।

लिंकर क्या करता है सभी मॉड्यूल को एक साथ देखने के लिए, यह देखें कि प्रत्येक मॉड्यूल को खुद को बाहर से कनेक्ट करने की आवश्यकता है, और सभी चीजों को देखें जो इसे निर्यात कर रहे हैं। यह तब सभी को ठीक करता है, और एक अंतिम निष्पादन योग्य बनाता है, जिसे तब चलाया जा सकता है।

जहां डायनेमिक लिंकिंग भी चल रही है, लिंकर का आउटपुट अभी भी चलने में सक्षम नहीं है - बाहरी लाइब्रेरियों के कुछ संदर्भ अभी भी हल नहीं हुए हैं, और वे उस समय ओएस द्वारा हल हो जाते हैं जब यह ऐप लोड हो जाता है (या संभवतः बाद में भी रन के दौरान)।


यह ध्यान देने योग्य है कि कुछ कोडांतरक या कंपाइलर एक निष्पादन योग्य फ़ाइल को सीधे आउटपुट कर सकते हैं यदि कंपाइलर "आवश्यक" सब कुछ देखता है (आमतौर पर एक एकल स्रोत फ़ाइल में कुछ भी #includes)। कुछ संकलक, आमतौर पर छोटे माइक्रो के लिए, ऑपरेशन के अपने एकमात्र मोड के रूप में होते हैं।
सुपरकाट

हां, मैंने बीच-बीच में जवाब देने की कोशिश की। बेशक, साथ ही साथ आपके मामले के विपरीत भी सच है, कि कुछ प्रकार की ऑब्जेक्ट फ़ाइल में पूर्ण कोड-पीढ़ी भी नहीं है; यह लिंकर द्वारा किया जाता है (यह कि MSVC पूरे प्रोग्राम-ऑप्टिमाइज़ेशन कैसे काम करता है)।
विल डीन

@WillDean और GCC का लिंक-टाइम ऑप्टिमाइज़ेशन, जहाँ तक मैं बता सकता हूँ - यह सभी 'कोड' को आवश्यक मेटाडेटा के साथ GIMPLE मध्यवर्ती भाषा के रूप में स्ट्रीम करता है, जो कि लिंकर को उपलब्ध कराता है, और अंत में एक ही बार में अनुकूलन करता है। (क्या पुराना दस्तावेज होने के बावजूद, केवल GIMPLE को डिफ़ॉल्ट रूप से स्ट्रीम किया जाता है, न कि ऑब्जेक्ट कोड के दोनों निरूपण के साथ पुराने 'वसा' मोड के बजाय।)
underscore_d

10

जब कंपाइलर एक ऑब्जेक्ट फ़ाइल का उत्पादन करता है, तो इसमें उस ऑब्जेक्ट फ़ाइल में परिभाषित प्रतीकों के लिए प्रविष्टियां शामिल होती हैं, और उन ऑब्जेक्ट फ़ाइल में परिभाषित नहीं किए गए प्रतीकों के संदर्भ। लिंकर उन लोगों को ले जाता है और उन्हें एक साथ रखता है (जब सब कुछ सही काम करता है) प्रत्येक फ़ाइल से सभी बाहरी संदर्भ प्रतीकों द्वारा संतुष्ट होते हैं जो अन्य ऑब्जेक्ट फ़ाइलों में परिभाषित होते हैं।

यह तब उन सभी ऑब्जेक्ट फ़ाइलों को एक साथ जोड़ देता है और प्रत्येक प्रतीकों को पते असाइन करता है, और जहां एक ऑब्जेक्ट फ़ाइल में किसी अन्य ऑब्जेक्ट फ़ाइल का बाहरी संदर्भ होता है, यह प्रत्येक प्रतीक के पते पर भरता है जहां यह किसी अन्य ऑब्जेक्ट द्वारा उपयोग किया जाता है। एक विशिष्ट मामले में, यह उपयोग किए गए किसी भी पूर्ण पते की तालिका भी बनाएगा, इसलिए लोडर उन पतों को "ठीक" कर सकता है जब फ़ाइल लोड हो जाती है (यानी, यह उन सभी में आधार लोड पता जोड़ देगा पते ताकि वे सभी सही स्मृति पते को देखें)।

काफी कुछ आधुनिक लिंकर्स कुछ "(कुछ मामलों में बहुत सारे )" अन्य सामानों को भी ले जा सकते हैं , जैसे कि कोड को उन तरीकों से अनुकूलित करना जो केवल सभी मॉड्यूल के दिखाई देने के बाद ही संभव हैं (उदाहरण के लिए, शामिल किए गए कार्यों को हटाना) क्योंकि यह संभव था कि कुछ अन्य मॉड्यूल उन्हें कॉल कर सकते हैं, लेकिन एक बार सभी मॉड्यूल को एक साथ रखने के बाद यह स्पष्ट होता है कि कुछ भी कभी भी उन्हें कॉल नहीं करता है)।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.