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.