Foundationপ্রথম নীতি থেকে
LEVEL 5লেসন ১৪/১৬কঠিন৫৫ মিনিট

Linking ও Relocation — ভাঙা address জোড়া লাগানো

Linking and Relocation

প্রতিটা .o ফাইল নিজের ভেতরের ঠিকানা জানে কিন্তু বাইরের প্রতিটা symbol-এর জায়গায় ফাঁকা ঘর রাখে — linker symbol resolution আর relocation-এর মাধ্যমে সেই ভাঙা ঠিকানাগুলো চূড়ান্ত মান দিয়ে জোড়া লাগায়।

এই লেসন শেষে আপনি পারবেন

  • Symbol table আর relocation table-এর ভূমিকা আলাদা করে ব্যাখ্যা করতে পারবেন — defined/undefined, global/local/weak symbol চেনা, nm/readelf -s আউটপুট পড়তে পারবেন
  • R_X86_64_PC32, PLT32 আর 64-bit absolute (R_X86_64_64) relocation-এর গাণিতিক সূত্র ব্যবহার করে readelf -r আউটপুটের প্রতিটা entry হাতে সমাধান করতে পারবেন
  • Static linker-এর symbol resolution algorithm ধাপে ধাপে ট্রেস করে বলতে পারবেন archive থেকে member কখন টানা হয়, আর 'undefined reference' ও 'multiple definition' ঠিক কোন ধাপে জন্ম নেয় ধরিয়ে দিতে পারবেন
  • Link-time আর load-time address-এর পার্থক্য ব্যাখ্যা করে PIE কেন link-time-এ base 0 ধরে বানানো হয় বলতে পারবেন
  • pyelftools দিয়ে সরল ELF relocatable object পড়ে .text merge করে, PC-relative relocation সমাধান করে, একটা চলমান executable emit করতে পারবেন

আগে যা বোঝা থাকা দরকার

আগে এটা বুঝি

গত লেসনে toy compiler প্রথম .s emit করল আর gcc দিয়ে চলল। কিন্তু gcc gen.s -o gen-এর ভেতরে দুইটা প্রোগ্রাম চলেছে — assembler আর linker। Assembler টেক্সট→byte ফেলে; চলার-যোগ্য executable বানানোর কঠিন কাজ — প্রতিটা instruction-কে চূড়ান্ত ঠিকানা দেওয়া, বাইরের call যুক্ত করা, startup বসানো — linker-এর।

ভাঙা address মানে: call helper-এর rel32 ঘরে assembler লিখে রাখে চারটা শূন্য — helper কোথায় বসবে সে জানে না। সে রেখে যায় একটা চিঠি — “পরে এই ঘরে ঠিকানা বসাও” — relocation entry। Linker-ের পুরো কাজ দুই প্রশ্নে: প্রতিটা নামের মালিক কে (symbol resolution), আর ভাঙা ঘরে কী সংখ্যা বসবে (relocation)।

আর কেন এক ফাইলে সব লিখে এই ঝামেলাই না করা? কারণ পৃথক পৃথক translation unit ছাড়া: incremental build অসম্ভব (এক লাইন বদলালে পুরো project recompile), library ভাগ করা অসম্ভব (printf-এর সোর্স প্রতিটা প্রোগ্রামে কপি করতে হবে), আর ভিন্ন ভাষায় লেখা অংশ জোড়া দেওয়া অসম্ভব। পৃথকীকরণই দাবি করে যে কেউ একজন পরে জোড়া লাগাবে — সেই কেউ-ই linker।

মূল ধারণা

দুইটা টেবিল — কে কী দেয়, কে কী চায়

প্রতিটা relocatable object (.o) — elf-format লেসনের ET_REL ELF — ভেতরে রাখে ভূমিকায় বিপরীত দুইটা টেবিল।

Symbol table (.symtab) — “আমার কাছে কী আছে, আমি কী চাই”:

$ nm math.o
0000000000000000 T add_values      # defined, .text, global
0000000000000000 D base            # defined, .data, global
0000000000000034 t square_it       # local (static)
                 U printf          # undefined

অক্ষরগুলোই সারাংশ — T/t: .text global/local, D/d: data, B: .bss, U: undefined, W/w: weak। Binding-এর তিন স্তর:

Bindingদেখেহয়
localএই .o-র ভেতরেইstatic
globalসব .o; এক নাম এক মালিকসাধারণ ফাংশন
weakসবাই, strong-এর কাছে হার মানে__attribute__((weak))

Relocation table (.rela.text, .rela.data) — “এই জায়গাগুলোতে ফাঁকা ঘর, এই নিয়মে ভরো”:

$ readelf -r math.o
Relocation section '.rela.text' contains 3 entries:
    Offset    Info         Type            Symbol's Name + Addend
000000000016  ...          R_X86_64_PLT32  printf - 4
00000000002a  ...          R_X86_64_PC32   .rodata - 4
000000000031  ...          R_X86_64_PC32   base - 4

প্রতিটা entry: কোথায় (Offset), কোন নাম (Symbol), কোন সূত্রে (Type)।

তিনটা সাধারণ relocation সূত্র

ABI-প্রতীক: S = symbol-এর চূড়ান্ত ঠিকানা, A = addend, P = ঠিক হওয়া ঘরের ঠিকানা।

Typeসূত্রকোথায়
R_X86_64_PC32S + A − PRIP-relative data access
R_X86_64_PLT32S + A − P, S = PLT entrycall বাইরের_ফাংশন
R_X86_64_64 (“ABS64”)S + A.data-র .quad symbol

PC32/PLT32 সূত্র এক — পার্থক্য অর্থে: PLT32 “দরকার হলে PLT entry দিয়ে যাও” (loader লেসনের গল্প); static exe-তে ফল এক। ABS64 পরম ঠিকানা, ৬৪ bit — কেবল data-তে, code-এ না (কোড PIC হতে চায়)।

উদাহরণ: লিংক-শেষে base = 0x402008, ঘর P = 0x401031, addend −4। PC32 সূত্রে ঘরে:

S+A−P=0x402008+(−4)−0x401031=0xFD3S + A - P = 0x402008 + (-4) - 0x401031 = 0xFD3

কোড থেকে যে RIP-relative access হবে, সেটা ঠিক তত দূরের base-কে ছুঁবে — কোনো absolute address কোথাও লেখা হলো না।

Static linking — archive থেকে member টানা

.a archive = .o-গুচ্ছ + index (কোন member কোন symbol define করে)। Resolution algorithm:

১. সরাসরি দেওয়া .o-গুলোর symbol টেবিল মিলাও
২. undefined প্রতিটা নামের জন্য archive index খোঁজো —
   define করা member পেলে শুধু তা-ই টেনে আনো
৩. নতুন member নতুন undefined আনতে পারে → ধাপ ২ ফেরত
৪. আর কিছু টানা হচ্ছে না → থামো
৫. শেষে undefined বাকি → undefined reference;
   একই strong নাম দুইবার → multiple definition

পরিণতি দুটি: archive-এর বেশিরভাগ member লিংক-ই হয় না (libc-র হাজার ফাংশনের হাতful টুকুই আসে), আর link order গুরুত্বপূর্ণ — -lfoo consumer-দের পরে না দিলে ধাপ ২-এ টানার পালাই আসে না।

ভেতরে কী ঘটছে

Symbol-এর ঠিকানা দুইবার জন্ম নেয়: link-time (linker-এর বাছাই করা virtual address) আর load-time (loader সত্যিই যেখানে বসাল)। Non-PIE যুগে দুটো এক — linker 0x400000 ধরলে loader সেখানেই বসাত। আধুনিক PIE (distro ডিফল্ট) চুক্তি বদলেছে:

link-time:   সব section বসাও base 0 থেকে শুরু করে
             → প্রতিটা ঠিকানা আসলে "শুরু থেকে দূরত্ব"

load-time:   kernel/ld.so একটা random base বেছে নেয় (ASLR)
             → পুরো image একসাথে সেই base-এ shift হয়
             → দূরত্ব অপরিবর্তিত, তাই PC-relative কোড সত্য থাকে

PC-relative ঘর লোড-ঠিকানা থেকে স্বাধীন; ABS64 pointer-এর কাজ load-time-এও বাকি — R_X86_64_RELATIVE relocation loader base যোগ করে সারায়।

Object anatomy — এক নজরে আবার

elf-format লেসনের সেই ছকটা এবার linker-এর চোখ দিয়ে:

Sectionlinker যা করে
.text/.rodataসব .o-র টুকরো জুড়ে এক output section, RO-X segment-এ
.data/.bssmerge; .bss ফাইলে নেই, মেমরিতে zero-fill
.symtabresolution-এ পড়ে, চূড়ান্ত বাইনারিতে প্রায়ই strip
.rela.text/dataসূত্র-তালিকা — merge-এর পরে apply
.debug_infoউপেক্ষা

ক্রম জরুরি: আগে সবাইকে চূড়ান্ত ঠিকানা, পরে relocation সূত্র — উল্টো হলে S, P-ই অজানা।

একটা call instruction-এর ঠিকানা কোথা থেকে কোথায় পূর্ণ হয়
  1. C সোর্স: helper();ঠিকানার ধারণাই নেই
  2. .s: call helper → .o: e8 00000000 + rela entryঘর বরাদ্দ, মান শূন্য
  3. Linker: S+A−P → e8 16 00 00 00link-time জন্ম
  4. Loader: base বাছে, image shiftload-time জন্ম — PC32 অক্ষত

দুইটা বিখ্যাত error — algorithm-এর দুই ব্যর্থতা

undefined reference — ধাপ ৫ ব্যর্থ: defined মেলেনি; কারণ তিন — লেখাই হয়নি, library লিংক লাইনে নেই, বা order-ভুল। Multiple definition — সংঘর্ষ: দুই strong global এক নামে; classic দোষী header-এ int counter; include করা — সংজ্ঞা .c-তে থাকার নিয়মের উৎস এটাই।

উদাহরণ

তিন-ফাইল project পুরো দৈর্ঘ্যে

// mathutil.h
int add_values(int a, int b);
// mathutil.c
int base = 10;
static int square_it(int x) { return x * x; }
int add_values(int a, int b) { return base + square_it(a) + b; }

// main.c
#include <stdio.h>
int add_values(int a, int b);
int main(void) {
    printf("result %d\n", add_values(3, 4));
    return 0;
}
$ gcc -c mathutil.c main.c
$ nm mathutil.o
T add_values            # export
D base
t square_it             # static — বাইরে অদৃশ্য
U printf                # import
$ nm main.o
T main
U add_values
U printf
$ readelf -r main.o     # PLT32 add_values -4, PLT32 printf -4, PC32 .rodata -4

main.o জানে নামটা, জানে না জায়গাটা; mathutil.o উল্টোটা। জোড়া:

$ gcc mathutil.o main.o -o app
$ ./app
result 23

শেষে ভাঙা ঘর সব ভরা — objdump-এ গণনা করা rel32; PIE বলে load address যা-ই হোক ঠিক থাকবে।

ছবিতে

Merge আর patch — এক ছবিতে

লিংকের আগে (তিনটা আলাদা জগৎ):

mathutil.o .text          main.o .text
┌──────────────────┐      ┌──────────────────┐
│ add_values:      │      │ main:            │
│   ...            │      │   call ???       │◄─ .rela.text:
│   call printf    │◄─┐   │   call ???       │   offset 0x0b, PLT32,
└──────────────────┘  │   │   ...            │   addend −4, নাম add_values
                      │   └──────────────────┘
.symtab: T add_values │
         U printf  ───┤   .symtab: T main
                            U add_values ──┐
                                           │
লিংকের পরে (এক জগৎ, base 0x400000 ধরে):    │
                                           │
app .text                                  │
┌───────────────────────────────────┐      │
│ 0x401000 main:                    │      │
│   call +offset ───────────────────┼──┐   │
│ 0x401020 add_values:              │  │   │
│   ...                             │  │   │
│   call +offset ───────────────────┼──┼─ printf-এ (libc-তে)
└───────────────────────────────────┘  │
        S(add_values)=0x401020 ◄───────┘
        ঘর P=0x401007 → বসল S+A−P

লক্ষণ: প্রতিটা .text অক্ষুণ্ণ কপি পাশাপাশি; patch শুধু ভাঙা ঘরে; printf-এর মতো library symbol ভাসমানই থাকতে পারে — বাঁধে dynamic linking (পরের লেসন)।

লিংক-পূর্ব তিনটা আলাদা জগৎ থেকে লিংক-পরবর্তী এক জগৎ — merge হয় অক্ষত কপি, patch শুধু ভাঙা ঘরে।

নিজে চালিয়ে দেখুন

EXPERIMENT

ভাঙুন ইচ্ছা করে — আর error-টা algorithm-এর ভাষায় পড়ুন

Linux (gcc, nm, readelf; macOS-এ nm চলে, readelf-এর বদলে WSL/VM ভালো)· ১৫ মিনিট

তিন-ফাইল project নিন, আগে স্বাস্থ্য-পরীক্ষা:

gcc -c mathutil.c main.c && gcc mathutil.o main.o -o app && ./app
# result 23

ভাঙা ১ — missing definition। main.c-তে add_values → add_valuez (শুধু main.c recompile):

gcc -c main.c && gcc mathutil.o main.o -o app
/usr/bin/ld: main.o: in function `main':
main.c:(.text+0xb): undefined reference to `add_valuez'
collect2: error: ld returned 1 exit status

Algorithm-ভাষায়: .text+0xb ঘরের নাম add_valuez, ধাপ ৫-এ defined মেলেনি। Offset পর্যন্ত বলে দেয় — objdump-এ 0xb-ই সেই call।

ভাঙা ২ — duplicate definition। mathutil.c আর extra.c দুটোতেই int base = 10;:

echo 'int base = 99;' > extra.c
gcc -c extra.c && gcc mathutil.o extra.o main.o -o app
# /usr/bin/ld: extra.o:(.data+0x0): multiple definition of `base`;
#   mathutil.o:(.data+0x0): first defined here

“first defined here” — সংঘর্ষ-বিন্দু: দুই strong global, এক নাম। extra.c-এর base static করলেই error গায়েব — local binding global symbol-table-তে ওঠেই না। Header-এ declaration, .c-তে definition — নিয়মটা linker-ের সাথে সরাসরি চুক্তি।

এটা কী প্রমাণ করে

undefined reference আর multiple definition — দুইটা ভয়ংকর দেখানো error-ই আসলে symbol resolution algorithm-এর দুইটা সুনির্দিষ্ট ব্যর্থতা-বিন্দু, আর error message-এর প্রতিটা শব্দ সেই algorithm-এর শব্দকোষ থেকেই আসে।

নিজে বানান

BUILD IT

mini-linker — দুইটা .o থেকে চলমান executable

Python (pyelftools) · ●●●○○
  1. দুইটা সরল relocatable object তৈরি করুন hand-written assembly থেকে
  2. pyelftools দিয়ে .text, .symtab, .rela.text পড়ুন
  3. .text-গুলো পরপর বসিয়ে merged image বানান, প্রতিটা global symbol-এর চূড়ান্ত ঠিকানা রেকর্ড করুন
  4. প্রতিটা PC32/PLT32 relocation-এ S + A − P গণনা করে ৪ byte patch করুন
  5. ELF header + একটা PT_LOAD program header লিখে ET_EXEC বানান, চালিয়ে exit code যাচাই করুন

লিংকারের হাত নিজে লাগাব — দুইটা .o, শুধু .text, শুধু PC-relative call। ইনপুট:

# start.s — ভবিষ্যতের entry point
    .text
    .globl _start
_start:
    movl $7, %edi
    call bump               # নাম আছে, জায়গা নেই
    movl %eax, %edi
    movl $60, %eax          # sys_exit
    syscall

# bump.s — ৭-এর পরের সংখ্যা
    .text
    .globl bump
bump:
    leal 1(%rdi), %eax
    ret
as start.s -o start.o        # অথবা: gcc -c start.s
as bump.s  -o bump.o
readelf -r start.o           # ... R_X86_64_PLT32  bump - 4 ← ভাঙা ঘর

এবার লিংকার (~120 লাইন):

from elftools.elf.elffile import ELFFile
import struct, sys

BASE = 0x400000
PT_LOAD, ET_EXEC, EM_X86_64 = 1, 2, 62
PF_X, PF_R = 1, 4

def load_obj(path):
    """.o থেকে .text bytes, symbols, relocations তুলে আনে"""
    with open(path, 'rb') as f:
        elf = ELFFile(f)
        text = elf.get_section_by_name('.text').data()
        syms = {}
        symtab = elf.get_section_by_name('.symtab')
        for s in symtab.iter_symbols():
            if (s.name and s['st_shndx'] != 'SHN_UNDEF'
                    and s['st_info']['type'] == 'STT_FUNC'
                    and s['st_info']['bind'] == 'STB_GLOBAL'):
                syms[s.name] = s['st_value']
        relocs = []
        for sec in elf.iter_sections():
            if sec.header['sh_type'] == 'SHT_RELA':
                for r in sec.iter_relocations():
                    sym = symtab.get_symbol(r['r_info_sym'])
                    relocs.append((r['r_offset'], r['r_info_type'],
                                   sym.name, r['r_addend']))
    return text, syms, relocs

objs = [load_obj(p) for p in sys.argv[1:3]]

# ধাপ ১ — layout: প্রতিটা .text-এর ভবিষ্যৎ ঠিকানা ঠিক করা
offsets, cursor = [], 0x100          # প্রথম 0x100 byte: দুইটা header
for text, _, _ in objs:
    cursor = (cursor + 15) // 16 * 16    # ১৬-এ align
    offsets.append(cursor)
    cursor += len(text)

# ধাপ ২ — symbol resolution: নাম → চূড়ান্ত ঠিকানা
defined = {}
for (text, syms, _), off in zip(objs, offsets):
    for name, val in syms.items():
        if name in defined:
            sys.exit(f"multiple definition of {name!r}")
        defined[name] = BASE + off + val

# ধাপ ৩ — merge .text এক বড় image-এ
image = bytearray(cursor)
for (text, _, _), off in zip(objs, offsets):
    image[off:off + len(text)] = text

# ধাপ ৪ — relocation: S + A − P দিয়ে ৪ byte patch
patched = 0
for (text, _, relocs), off in zip(objs, offsets):
    for r_offset, rtype, name, addend in relocs:
        if rtype not in (2, 4):      # R_X86_64_PC32 / PLT32
            sys.exit(f"অসমর্থিত type {rtype} ({name})")
        if name not in defined:
            sys.exit(f"undefined reference to {name!r}")
        S = defined[name]
        P = BASE + off + r_offset
        rel = (S + addend - P) & 0xFFFFFFFF
        site = off + r_offset
        image[site:site + 4] = struct.pack('I', rel)
        patched += 1

# ধাপ ৫ — executable emit: header দুটোই image-এর প্রথম 0x100 byte-এ
entry = defined['_start']
ehdr = b'\x7fELF' + bytes([2, 1, 1, 0]) + b'\x00' * 8
ehdr += struct.pack('HHIQQQIHHHHHH',
                    ET_EXEC, EM_X86_64, 1,
                    entry, 64, 0, 0,
                    64, 56, 1, 0, 0, 0)
phdr = struct.pack('IIQQQQQQ',
                   PT_LOAD, PF_R | PF_X, 0, BASE, BASE,
                   len(image), len(image), 0x1000)
image[0:len(ehdr)] = ehdr
image[len(ehdr):len(ehdr) + len(phdr)] = phdr
# এখন সার্বিক নিয়ম: file offset X ↔ virtual address BASE + X

out = sys.argv[3] if len(sys.argv) > 3 else 'mini'
with open(out, 'wb') as f:
    f.write(bytes(image))
import os
os.chmod(out, 0o755)
print(f"লিংক শেষ: {patched}টি relocation patch, entry={entry:#x}")
python3 minilink.py start.o bump.o mini
# লিংক শেষ: 1টি relocation patch, entry=0x400100
./mini; echo "exit=$?"
# exit=8

চলল — bump(7)=8 exit code-এ। ভাঙা ঘরে কী বসল, নিজের চোখে:

$ xxd -s 0x100 -l 24 mini
00000100: bf07 0000 00e8 1600 0000 89c7 b83c 0000
                        ^^^^^^^^^^^^
                        call-এর rel32 = 0x00000016 = 22

হাতে যাচাই: bump=0x400120, P=0x400106 → S+A−P = 0x400120−4−0x400106 = 22; execution-এ next-instruction 0x40010a + 22 = 0x400120 — ঠিক bump। এই চার byte-ই linking-এর হৃদস্পন্দন।

নিজে বাড়ান:

  1. .data merge + R_X86_64_64 (ABS64): .quad bump-এর জন্য S + A নিজে বাস্তবায়ন করুন
  2. ইচ্ছা করে ভাঙুন — bump.o বাদ (আপনার undefined reference message), দুইটা start.o (multiple definition)
  3. readelf -h/-l mini দিয়ে নিজের header ফিল্ড হাতে যাচাই — e_type, e_entry, PT_LOAD p_vaddr

বাস্তব সিস্টেমে

Linker যেখানে বাস্তবে দৌড়ায়

mold, lld, gold। বড় C++ project-এর মিনিট-লম্বা লিংক parallel linker-রা দশ-বিশ গুণ দ্রুত করেছে — বাধা ছিল বাস্তবায়নে, algorithm-এ না।

Game console/embedded। Console SDK প্রায় পুরোটাই static — predictable, ছোট runtime, loader attack-surface নেই।

LTO। .o-তে IR রেখে linker-ই পুরো প্রোগ্রাম optimize করে — compile/link সীমানা নরম।

Plugin/.so। Runtime-এ object জোড়া — এই লেসনের নিয়মেরই বিস্তৃতি, পরের লেসনের খাবার।

যে ভুলগুলো সবাই করে

“Linker শুধু ফাইলগুলো পাশাপাশি জুড়ে দেয়।”

জোড়া সবচেয়ে সস্তা অংশ। আসল দুই কাজ: resolution (নামের চূড়ান্ত মালিক — ব্যর্থলে undefined/multiple error) আর relocation (ভাঙা ঘরে সূত্র-মাফিক সংখ্যা); সাথে section order, alignment, versioning-এর হাজার সিদ্ধান্ত।

“Static library লিংক করলে পুরো .a বাইনারিতে ঢুকে যায়।”

উল্টো — archive extraction নির্ভর করে শুধু দরকারি member-এ। libc.a কয়েক মেগাবাইট, অথচ একটা hello world-এ তার থেকে আসে কয়েক কিলোবাইট: যে .o member-গুলো চাওয়া symbol define করে শুধু সেগুলোই টানা হয় (এবং তাদের নিজেদের দরকারগুলোও পর্যায়ক্রমে)। “লিংক হলোই সব ঢুকল” ভাবলে বাইনারি সাইজ বোঝা অসম্ভব হয়ে যায়।

“'undefined reference' মানেই library install করা হয়নি।”

তিন কারণেই এক error: define-ই নেই; লিংক লাইনে ফাইল নেই; অথবা order-ভুল — ফাইল আছে কিন্তু consumer-দের পরে, index-খোঁজার পালা আসেনি। তৃতীয়টায় -lfoo সরালেই ঠিক — package লাগে না।

“Relocation শুধু link-time-এ একবার হয়।”

Static non-PIE-তে হ্যাঁ। PIE/.so-তে অনেক relocation “load-time-এ base যোগ করো” বলে থাকে — loader প্রতি launch-এ প্রয়োগ করে; lazy GOT entry তো প্রথম call-এও পূর্ণ হয়। Link-time প্রথম জন্ম, শেষ নয়।

বুঝেছেন কি না দেখুন

1

nm-এর আউটপুটে T, t, D, B, U, W — প্রতিটা অক্ষর কী বোঝায়? কোনটা দেখলে বুঝবেন ওই .o অন্য কারো উপর নির্ভরশীল?

স্মরণ

T/t: .text global/local; D/d: data; B: .bss; U: undefined — বাইরে আসবে; W/w: weak। নির্ভরশীলতার প্রমাণ শুধু U — প্রতিটা U একটা ভাঙা ঘর, resolution-এ পূর্ণ হবে।

2

একটা R_X86_64_PC32 entry: symbol base, addend −4, ঘরটা বসবে 0x401031-এ। লিংক-শেষে base-এর ঠিকানা দাঁড়াল 0x402008। ঘরে কী সংখ্যা (hex) বসবে? সেই ঘর থেকে RIP-relative access-টা কীভাবে base-এ পৌঁছায়?

প্রয়োগ

0x402008 + (−4) − 0x401031 = 0xFD3; little-endian bytes d3 0f 00 00। Access-এ RIP (= P+4 = 0x401035) + 0xFD3 = 0x402008 — ঠিক base। Addend −4-ই “next instruction”-এর সমন্বয়, তাই call/lea-তে প্রায়ই −4।

3

gcc main.o -lfoo ব্যর্থ — undefined reference to foo_fn। কিন্তু gcc -lfoo main.o সফল! কী ঘটছে, আর কোন ধাপে?

যুক্তি

Linker বাম→ডান চলে: -lfoo-এর সময় foo_fn-এর চাহিদা এখনো জমেনি (main.o অপঠিত), তাই member টানা হয়নি; পরে U খালি থেকে যায়। উল্টো করলে U আগে জমে, index খোঁজা যায় — মেটে। নিয়ম: consumer আগে, provider পরে।

4

PIE executable-এর সব PC32 relocation কি load-time-এ আবার patch করতে হয়? না হলে বাইনারি অন্য ঠিকানায় লোড হলে কীভাবে সঠিক থাকে?

যুক্তি

না — PC32 ঘরগুলো লিংক-শেষে চূড়ান্ত, লোডে কোনো স্পর্শ হয় না। কারণ সূত্রটা দূরত্বের: S + A − P-এ S আর P দুটোই একই base থেকে মাপা, তাই base বদলালে দুটো একই পরিমাণে বদলায় আর পার্থক্য অপরিবর্তিত থাকে। যে ঘরগুলো load-time-এ দরকার সেগুলো absolute (R_X86_64_64-জাতীয়) — সেগুলোর জন্যই dynamic relocation তালিকা রেখে যেতে হয়। এই অর্থনীতিই PIE-কে সস্তা করেছে: বেশিরভাগ ঘর একবারই ঠিক হয়।

5

আপনার mini-linker-এ এখন দুইটা সীমাবদ্ধতা: .data merge নেই আর alignment নেই। কোনটা আগে যোগ করবেন এবং কেন? .data যোগ করলে কোন নতুন relocation type সামলাতে হবে?

ডিজাইন

Alignment আগে — কারণ এটা সঠিকতার প্রশ্ন: x86-64-এ misaligned code চলে বটে, কিন্তু .data-র ৮-byte ভ্যালু misaligned হলে performance ক্ষতি এবং ভবিষ্যতে atomic/SIMD অপারেশনে fault-এর ঝুঁকি; প্রতিটা section-এর sh_addralign মেনে cursor round-up করাই সস্তা সংশোধন। .data merge-এ পালা আসবে R_X86_64_64 (ABS64)-এর — সূত্র S + A (পার্থক্য নয়, পরম মান) — এবং সঙ্গে সিদ্ধান্ত: এই ঘর PIE-তে load-time-এ সরাতে হবে, তাই output-এ dynamic relocation entry-ও লিখতে হবে। এক ধাপে দুইটা নতুন ধারণা — তাই আগে alignment।

এরপর কী

এরপর কী

Mini-linker সরাসরি-চলা ET_EXEC বানাল — ঠিকানা লিংক-টাইমেই চূড়ান্ত বলে। কিন্তু gcc-বানানো বাইনারিতে kernel PT_INTERP দেখে dynamic linker-কে ডাকে — “অসম্পূর্ণ relocation তোমার।” সেই দ্বিতীয় জোড়া, GOT/PLT lazy binding, ./a.out থেকে main() পর্যন্ত যাত্রা — পরের লেসনে।

আরও পড়ুন

  • Linkers and Loaders — John R. Levine · Symbol resolution আর relocation-এর সবচেয়ে পঠনীয় প্রামাণ্য আলোচনা — Chapter 5 ও 6 এই লেসনের মেরুদণ্ড
  • System V ABI — Generic ABI, Chapter 4 (Object Files) · Relocation entry-র সংজ্ঞা ও x86-64 type-সূত্র — S, A, P-এর উৎস
  • "Linkers" essay series — Ian Lance Taylor · gold linker-এর রচয়িতার বাস্তব-linker সিদ্ধান্তের ধারাবাহিক