İçeriğe geç
academia.sh

Ders 13 / 16

Bağlayıcı Seçenekleri

Kaynakta hiçbir yerde yazılı olmayan bir dizgi değerinin, üç ayrı derleme çağrısında üç ayrı biçimde ikiliye gömüldüğü ölçülür; kaynak metin üçünde de birebir aynı kalır. Sınırlayıcı ölçüm, gömülen değerin bağlayıcı tarafından hiç doğrulanmadığını — biçimsiz bir dizginin de sessizce kabul edildiğini — ve ayrı bir bağlayıcı seçeneğinin sembol tablosunu silerken programın çıktısını değiştirmediğini koşturarak gösteriyor.

Yayın Güncelleme

Bu derste

  • Kaynakta hiç yazılmayan bir dizgi değerinin, aynı kaynaktan üç ayrı çağrıyla üç farklı biçimde ikiliye gömüldüğünü ölçer
  • Bağlayıcının gömülen değeri hiç doğrulamadığını, biçimsiz bir girdiyi de sessizce kabul ettiğini gösterir
  • Sembol tablosunu kaldıran bir bağlayıcı seçeneğinin programın çıktısını değiştirmediğini koşturarak ayırt eder
İçindekiler

Önceki derste seçilen hedef ikiliye hiçbir iz bırakmadan, yalnızca derleme sürecinin kendi araçlarıyla belirleniyordu. Bu ders aynı örüntüyü, bu kez bir dizin ya da bir mimari yerine tek bir dizgi değeri için sürdürüyor: kaynakta hiçbir yerde yazılı olmayan bir değer, yine derleme anında, yine kaynağın dışından ikiliye giriyor. Bu kez soruyu soran taraf derleyici değil bağlayıcı — kaynağın derlendiği son adımda çalışan, ayrı derlenmiş parçaları tek bir ikilide birleştiren araç.

Kaynakta Olmayan Bir Değer

Bir paket düzeyi değişken, kaynakta bir varsayılan değerle bildirilebilir ve bu değer bağlayıcı seçeneği (linker flag) ile derleme anında değiştirilebilir. Aşağıdaki ölçüm tek bir kaynağı — bir tek surum değişkeni ve onu yazdıran bir main işlevi — üç ayrı derleme çağrısıyla çalıştırıyor. Kaynak metin üçünde de birebir aynı; değişen tek şey, komuta verilen bir bayrak.

Bayrağın kendisi bir eşitlik ifadesi taşıyor: değişkenin tam paket yolu, bir nokta, adı, sonra istenen değer. Bağlayıcı, ayrı ayrı derlenmiş paket parçalarını tek bir ikilide birleştirirken, her paketin dışa açtığı adları zaten bir tabloda tutuyor — bu tablo normalde paketler arasındaki çağrıları birbirine bağlamak için var. -X seçeneği bu aynı tabloyu bir başka amaçla kullanıyor: adı tabloda bulup, kaynaktaki ilk değerin yerine verilen dizgiyi yerleştiriyor. Bu yüzden hedef adın tam ve doğru yazılması gerekiyor — paket yolu yanlışsa ya da adın kendisi yanlış yazılmışsa, bağlayıcı genellikle hiçbir eşleşme bulamıyor ve bayrağı sessizce yok sayıyor; kaynaktaki varsayılan değer olduğu gibi kalıyor.

// yardimcilar.go — bu dersin paylastigi yardimcilar: gecici paket kurma ve arac cagirma
package main

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
)

// goster bir bilgiyi, kimin soyledigini ve kanitini tek satirda yazdirir.
func goster(bilgi, soyleyen, kanit string) {
	fmt.Printf("%-38s %-7s %s\n", bilgi, soyleyen, kanit)
}

// paket, verilen dosyalari yeni bir gecici modulun icine yazar; yol hicbir yerde basilmaz.
func paket(dosyalar map[string]string) string {
	dizin, err := os.MkdirTemp("", "baglayici")
	if err != nil {
		return ""
	}
	os.WriteFile(filepath.Join(dizin, "go.mod"), []byte("module baglayicideneme\n\ngo 1.24\n"), 0o644)
	for ad, icerik := range dosyalar {
		os.WriteFile(filepath.Join(dizin, ad), []byte(icerik), 0o644)
	}
	return dizin
}

// aracCalistir dilin kendi komutunu kosturur, ciktiyi ve basari durumunu doner.
func aracCalistir(dizin string, arg ...string) (string, bool) {
	cmd := exec.Command("go", arg...)
	cmd.Dir = dizin
	cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local")
	cikti, err := cmd.CombinedOutput()
	return string(cikti), err == nil
}

// calistir, kaynagi verilen baglayici degeriyle calistirir ve yazdirdigi tek satiri doner.
// uygula false ise hicbir -X bayragi verilmez; true ise deger bos dizgi bile olsa uygulanir.
func calistir(dizin string, uygula bool, deger string) string {
	arg := []string{"run", "."}
	if uygula {
		arg = []string{"run", "-ldflags=-X main.surum=" + deger, "."}
	}
	cikti, _ := aracCalistir(dizin, arg...)
	return strings.TrimRight(cikti, "\n")
}

// sembolVarMi, verilen ek baglayici bayragiyla derlenen ikilide bir sembol adinin
// bulunup bulunmadigini soyler; ikilinin kendisi ve yolu hicbir yerde basilmaz.
func sembolVarMi(dizin, ekBayrak, sembolAdi string) bool {
	ikiliYolu := filepath.Join(dizin, "ikili")
	arg := []string{"build", "-o", ikiliYolu}
	if ekBayrak != "" {
		arg = append(arg, "-ldflags="+ekBayrak)
	}
	arg = append(arg, ".")
	if _, tamam := aracCalistir(dizin, arg...); !tamam {
		return false
	}
	cikti, _ := aracCalistir(dizin, "tool", "nm", ikiliYolu)
	return strings.Contains(cikti, sembolAdi)
}
// olcum1.go — kaynakta yazili olmayan bir deger, ayni kaynaktan uc kez farkli okunuyor
package main

import "fmt"

const kaynakMetni = "package main\n\nimport \"fmt\"\n\nvar surum = \"gelistirme\"\n\nfunc main() {\n\tfmt.Println(surum)\n}\n"

func main() {
	dizin := paket(map[string]string{"prog.go": kaynakMetni})
	defer func() { _ = dizin }()

	fmt.Println("-- ayni kaynak, uc farkli baglayici girdisi --")
	goster("hicbir deger verilmeden", "arac", calistir(dizin, false, ""))
	goster("birinci deger verildiginde", "arac", calistir(dizin, true, "surum-a"))
	goster("ikinci deger verildiginde", "arac", calistir(dizin, true, "surum-b"))
}
-- ayni kaynak, uc farkli baglayici girdisi --
hicbir deger verilmeden                arac    gelistirme
birinci deger verildiginde             arac    surum-a
ikinci deger verildiginde              arac    surum-b

DR9. kaynakMetni sabiti üç çalıştırmanın üçünde de karakter karakter aynı; değişen tek şey calistir işlevine verilen bayrak. Kaynakta yazılı olan tek değer "gelistirme" — bu, hiçbir bayrak verilmediğinde görünen değer, yani kaynak sınıfına giren tek satır. Öteki iki satır kaynakta hiç yok; bağlayıcı, derleme sırasında paket düzeyi bir dizgi değişkeninin ilk değerini, kaynağı hiç değiştirmeden, dışarıdan verilen bir dizgiyle değiştiriyor. Bu üç satırın hepsi araç sınıfına yazılıyor, çünkü değer her çağrıda aynı bayrakla aynı sonucu veriyor — koşumdan koşuma değişen bir şey yok, değişen yalnızca hangi bayrağın verildiği.

Bu düzeneğin çalışabilmesi için iki koşul birden gerekiyor. Değişken paket düzeyinde olmalı — bir işlevin içindeki yerel bir değişken bağlayıcının erişemeyeceği bir yerde duruyor. Ve değişkenin tipi dizgi olmalı; bağlayıcı yalnızca dizgi değerlerini bu şekilde değiştirebiliyor, bir tam sayıyı ya da bir yapının alanını aynı yoldan değiştiremiyor. Bu iki kısıt, düzeneği kasıtlı olarak dar tutuyor: karmaşık bir veri yapısını değil, tek bir metin parçasını derleme anında yerleştirmek için var.

Bu darlık bir eksiklik değil, bir tasarım kararı. Bağlayıcı, kaynağı hiç ayrıştırmadan, yalnızca derlenmiş nesne dosyalarının içindeki ikili temsille çalışıyor — bir dizgi değişkeninin ikili temsili, uzunluğu ve baytları önceden bilinen bir bellek bloğu, bu yüzden bağlayıcının yapması gereken tek şey o bloğun içeriğini değiştirmek. Bir tam sayı ya da bir yapı için aynı işlem çok daha kırılgan olurdu: değerin tipini ve büyüklüğünü bilmeden rastgele bir baytı yerine koymak, ikilinin geri kalanını bozma riski taşırdı. Dizgi tipi bu riski taşımıyor, çünkü Go’nun dizgi temsili zaten bir uzunluk alanıyla birlikte tutuluyor ve bağlayıcı yeni değerin uzunluğunu da bu alana yazabiliyor.

Sınırlayıcı Ölçüm: Gömülen Değer Doğrulanmıyor

Yukarıdaki üç değer de biçimli, okunaklı dizgilerdi. Bağlayıcının kendisi bu biçime hiç bakmıyor — verilen her şeyi olduğu gibi yerleştiriyor. Aşağıdaki ölçüm bunu iki uçla sınıyor: bir yandan hiçbir tanınabilir kalıba uymayan bir dizgi, öte yandan tümüyle boş bir dizgi.

// olcum2.go — sinirlayici olcum: gomulen deger dogrulanmiyor, sembol tablosu ayri bir sey
package main

import "fmt"

const kaynakMetni = "package main\n\nimport \"fmt\"\n\nvar surum = \"gelistirme\"\n\nfunc main() {\n\tfmt.Println(surum)\n}\n"

func main() {
	dizin := paket(map[string]string{"prog.go": kaynakMetni})
	defer func() { _ = dizin }()

	fmt.Println("-- sinirlayici olcum: gomulen deger dogrulanmiyor --")
	goster("bicimsiz bir deger verildiginde", "arac", calistir(dizin, true, "###GECERSIZ-SURUM-999"))
	goster("bos dizgi verildiginde", "arac", "["+calistir(dizin, true, "")+"]")

	fmt.Println()
	fmt.Println("-- ikinci baglayici secenegi: sembol tablosunu cikarmak --")
	goster("normal derlemede sembol var mi", "arac", fmt.Sprint(sembolVarMi(dizin, "", "main.surum")))
	goster("-s ile derlemede sembol var mi", "arac", fmt.Sprint(sembolVarMi(dizin, "-s -w", "main.surum")))
	goster("-s ile davranis degisiyor mu", "arac", "cikti="+calistir(dizin, false, ""))
}
-- sinirlayici olcum: gomulen deger dogrulanmiyor --
bicimsiz bir deger verildiginde        arac    ###GECERSIZ-SURUM-999
bos dizgi verildiginde                 arac    []

-- ikinci baglayici secenegi: sembol tablosunu cikarmak --
normal derlemede sembol var mi         arac    true
-s ile derlemede sembol var mi         arac    false
-s ile davranis degisiyor mu           arac    cikti=gelistirme

DR10. Birinci satır, bir sürüm numarası gibi görünmeyen bir dizgiyi hiçbir uyarı, hiçbir red olmadan kabul etti; ikinci satır tümüyle boş bir dizgiyi de aynı şekilde kabul etti — köşeli parantezlerin arası boş çünkü değişkenin kendisi boş bir dizgiye eşitlendi. Bağlayıcı burada bir tür denetimi ya da bir biçim denetimi yapmıyor; yaptığı iş, verilen bayttan diziyi kaynak koddaki ilk değerin yerine koymaktan ibaret. Bu, önceki dersteki “derlenmek çalışmak değildir” sınırının bir başka görünümü: bağlayıcı geçtiyse, gömülen değerin anlamlı olduğu hiç garanti edilmiyor — yalnızca doğru yere, doğru tipte yerleştirildiği garanti ediliyor.

Bu boşluğu kapatmanın tek yolu, gömülen değeri kaynağın dışında, ayrı bir adımda denetlemek — bağlayıcının kendisi bu işi hiçbir zaman üstlenmiyor. Bir dağıtım sürecinde bu genellikle gömülecek dizginin, çağıran tarafından önceden bilinen bir kalıba uyup uymadığının, bağlayıcı çağrılmadan önce sınanması biçiminde çözülüyor — tıpkı bu dersin ölçtüğü gibi, sınama bağlayıcının kendisinden değil, onu çağıran sürecin kendi disiplininden geliyor. Bu ders o disiplini uygulamadı, kasıtlı olarak: amaç bağlayıcının kendi davranışını, çevresindeki hiçbir denetim katmanı olmadan çıplak haliyle görmekti.

DR11. İkinci ölçüm ayrı bir bağlayıcı seçeneğine bakıyor: sembol tablosunu ve hata ayıklama bilgisini kaldıran bayrak. Normal derlemede main.surum adı ikilinin sembol tablosunda bulunuyor — bu ad, bir hata ayıklayıcının ya da bir çözümleme aracının değişkeni tanımasını sağlıyor. Aynı bayrak eklendiğinde sembol tablosundan bu ad siliniyor, ama programın kendi çıktısı — son satırdaki calistir çağrısı — hiç değişmiyor. Bu, iki farklı bağlayıcı seçeneğinin iki farklı katmanda çalıştığını gösteriyor: biri programın verisini değiştiriyor (-X), öteki yalnızca programın hakkındaki bilgiyi siliyor (-s) — ikisi birlikte kullanılabiliyor ve biri ötekinin sonucunu hiç etkilemiyor.

Bu ayrım rastgele değil: -s bayrağının kaldırdığı sembol tablosu ve hata ayıklama bilgisi, programın kendi mantığının hiçbir parçası değil — bir hata ayıklayıcının değişken adlarını, işlev adlarını ve satır numaralarını okuyabilmesi için ikiliye eklenen, ama çalışma zamanında programın kendisi tarafından hiç kullanılmayan ek veriler. -X ise tam tersi bir yere, programın kendi çalışma zamanı verisinin içine yazıyor. İki seçenek aynı komutta birlikte verildiğinde bile aralarında bir sıralama sorunu çıkmıyor, çünkü sembol tablosu derleme sürecinin sonunda, bütün değerler zaten yerlerine oturmuşken siliniyor — silinen şey adların kendisi, adların işaret ettiği veriler değil.

Kaynakta Bir İz Bırakmadan

İki ölçümün ortak noktası, bağlayıcının çalıştığı katmanın kaynağın kendisinden ayrı olması. Kaynak dosyası hem surum-a hem surum-b hem ###GECERSIZ-SURUM-999 çalıştırmalarında birebir aynı kaldı; hiçbiri diskte, kaynağın hiçbir versiyonunda görünmüyor. Bu, önceki dersin sonunda işaret edilen paralelliği tamamlıyor: hedef seçimi ikiliye nasıl hiçbir iz bırakmadan giriyorsa, gömülen bir dizgi de kaynağa hiçbir iz bırakmadan giriyor. İkisi de derleme sürecinin kendi araçlarıyla, derleme girdisinin bir parçası olarak uygulanıyor — kaynağın çıktısının değil.

Bunun pratik sonucu, aynı kaynak ağacından üretilen her ikilinin, kendine özgü bir “kimlik” taşıyabilmesi: bir sürüm dizgisi, bir yapı zaman damgası, bir dağıtım kimliği gibi değerler kaynağı hiç değiştirmeden, yalnızca derleme çağrısı değiştirilerek her ikiliye ayrı ayrı verilebiliyor. Bedeli de aynı yerde duruyor: bu değerlerin doğruluğu kaynak denetiminin dışında kalıyor, bir bakan-denetleyen kişi ya da süreç olmadan yanlış bir değer sessizce ikiliye girebiliyor. Bir sonraki derste kaynakta gerçekten yazılan bir sürüm bilgisiyle karşılaştırıldığında bu fark daha keskin görünüyor: orada sürüm numarası kaynağın kendisinde durur ve bir araç yalnızca onu denetler, burada sürüm benzeri değer kaynakta hiç durmaz ve hiçbir araç onu denetlemez.

Bu iki uç arasındaki seçim genellikle şu soruya bağlı: değer, kaynağın kimliğinin bir parçası mı, yoksa kaynağın hangi anda, hangi ortamda derlendiğinin bir parçası mı? Bir kütüphanenin taşıdığı bir sürüm numarası birincisine örnek — o değer kaynakla birlikte sürüyor, kaynak değişmeden değişmiyor, bu yüzden kaynakta yazılı olması daha tutarlı bir kayıt bırakıyor. Bir yapı kimliği ya da bir derleme ortamının bir bilgisi ikincisine örnek — bu değer kaynaktan değil, o anki derleme çağrısından geliyor ve her derlemede farklı olması beklenebiliyor; kaynağa yazmak bu durumda kaynağı, aslında ona ait olmayan bir bilgiyle kirletmek anlamına geliyor. Bağlayıcı seçeneği tam bu ikinci durumu, kaynağı hiç dokunmadan karşılıyor.

Özet

  • Paket düzeyinde bildirilen bir dizgi değişkeni, kaynak hiç değişmeden, bağlayıcı seçeneğiyle derleme anında farklı bir değerle doldurulabiliyor; bu değer kaynakta hiçbir zaman yazılı değil.
  • Aynı kaynak üç ayrı çağrıyla üç ayrı çıktı verdi; fark yalnızca verilen bayraktaydı, kaynak dosyanın hiçbir sürümünde değil.
  • Sınırlayıcı ölçüm: bağlayıcı gömülen değeri hiç doğrulamıyor — biçimsiz bir dizgi de, boş bir dizgi de aynı şekilde, hiçbir uyarı üretmeden kabul ediliyor.
  • Sembol tablosunu kaldıran ayrı bir bağlayıcı seçeneği, programın kendi çıktısını değiştirmiyor; iki seçenek birbirinden bağımsız iki katmanda çalışıyor.
  • Hedef seçimi (önceki ders) ve gömülen değer (bu ders) aynı örüntüyü paylaşıyor: ikisi de derleme girdisinin bir parçası, kaynağın kendisinin bir parçası değil.

Sonraki Adım

Bu derste gömülen değer kaynakta hiç yazılı değildi ve hiçbir araç onu denetlemedi. Sıradaki ders tam tersi bir durumu ele alıyor: bir sürüm numarası kaynağın kendisinde, açıkça bildiriliyor — ama bu kez bir araç, o sürümün taşıdığı bir sözü, yani eski koda karşı hâlâ uyumlu olup olmadığını denetliyor. Soru artık “değer nereden geliyor” değil, “kaynakta yazılan bir sayı neyi garanti ediyor”.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat