Yerel bir Mac eklentisinin paketlenmiş bağımlılığını gizli tutmak için uygulamasını gizli görünürlükle derleyin ve son bağlantı aşamasında yalnızca eklentinin herkese açık giriş noktalarını dışa aktarın. Bu denetimleri, bağımlılığı gerçekten içeren ikili dosyaya uygulayın.
Bu kılavuz, bir C veya C++ bağımlılığı içeren ve çalışma zamanında yüklenen bir eklenti geliştiren Mac masaüstü yazılımı geliştiricilerine yöneliktir. Somut amaç, eklentinin kendi uygulamasını korurken ana makineye yönelik küçük bir arayüzü dışa sunmaktır. Örnek, bir C bağımlılığını C++ eklentisine derler ve hem sonucu hem de dışa aktarılan simgeleri denetler.
Hangi ikili dosyanın dışa aktarımları sınırlandırılmalı?
Önce bağımlılığın makine kodunun nereye yerleştiğini belirleyin. Statik arşiv, bağlanmakta olan ikili dosyaya nesne dosyaları sağlar; dinamik kitaplık ise ayrı bir görüntü olarak kalır. Bu nedenle bir eklentideki simgeleri gizlemek, ayrı bir bağımlılık dylib'inin dışa aktarma tablosunu yeniden yazamaz. Apple bu ayrımı Dynamic Library Usage Guidelines belgesinde açıklar.
| Bağımlılık düzeni | Denetimin uygulanacağı yer | Denetlenecekler |
|---|---|---|
| Bağımlılık kaynak kodu eklentiye derleniyor | Bağımlılığın derlenmesi ve eklentinin son bağlantı aşaması | Özel tanımlar mevcut ancak dışa aktarılmıyor |
| Eklentiye bağlanan statik arşiv | Mümkünse arşivin derlenmesi ve eklentinin son bağlantı aşaması | Gerekli arşiv üyeleri dahil edilmiş; bağımlılık dışa aktarımları yok |
| Ayrı bağımlılık dylib'i | Bağımlılık dylib'inin kendi derlemesi | Herkese açık arayüzü ve eklentinin kaydedilmiş içe aktarımları |
| C++ başlıklarından örneklenen bağımlılık kodu | Bu başlıkları derleyen her hedef | Üretilen satır içi işlevler, şablonlar ve bunlarla ilişkili veriler |
Bu tablo, derleme ve bağlantı sınırlarından çıkarılmış bir karar aracıdır. Örneğin, bir sarmalayıcı hedefe eklenen dışa aktarma listesi başka bir hedefte üretilen simgeleri otomatik olarak gizlemez.
macOS varsayılan olarak iki düzeyli bir ad alanı da kullanır: dylib'e göre çözümlenen başvurular, bunları hangi kitaplığın sağladığını kaydeder. Düz ad alanı bu davranışı değiştirir. Zayıf dış tanımlar da yükleyicinin bunları birleştirebilmesi nedeniyle dikkate alınmalıdır. Apple her iki mekanizmayı da bağlayıcı kılavuzunda belgeler. Bu nedenle yalnızca adların eşleşmesi, eklentinin neden beklenmeyen bir uygulamayı çağırdığını göstermez.
Gizli derlemeyi açık bir dışa aktarma listesiyle birleştirin
Derleyici ve bağlayıcı denetimleri farklı aşamalarda çalışır. Clang'ın -fvisibility=hidden seçeneği, derlenen kaynak dosyalarındaki genel tanımların varsayılan görünürlüğünü belirler. Mevcut bir arşivi yeniden derlemez. Clang, satır içi C++ üye işlevleri için -fvisibility-inlines-hidden seçeneğini ayrıca belgeler; bunun uygunluğunu arayüze göre değerlendirilmesi gereken hedefli bir seçenek olarak ele alın. Clang komut satırı başvurusuna bakın.
Amaçlanan herkese açık giriş noktalarını __attribute__((visibility("default"))) ile işaretleyin. Görünürlük öznitelikleri derleme varsayılanını geçersiz kılabilir; bu nedenle bağımlılık başlıklarını açık dışa aktarma açıklamaları açısından inceleyin. Apple bu davranışı Controlling Symbol Visibility belgesinde açıklar.
Son bağlantı aşamasında, herkese açık arayüzü listelemek için -exported_symbols_list kullanın. Apple'ın belgelenmiş dışa aktarma listesi biçiminde, C simgelerinin başındaki alt çizgi de dahil olmak üzere bağlayıcı simge adları kullanılır. İki stratejiyi birleştirmek yerine dışa aktarma listesini ya da dışa aktarılmayan simgeler listesini seçin. Dynamic Library Design Guidelines belgesine bakın.
Küçük bir eklenti arayüzü için açık bir dışa aktarma listesi incelemeyi kolaylaştırır: dışa aktarılan her adın herkese açık olmasını gerektiren bir gerekçesi olmalıdır. Kaynak açıklamalarını listeyle tutarlı tutun; açıklama API'yi kodda belirtir, liste ise son ikili dosyayı sınırlar.
Eksiksiz bir yerel örnek oluşturun
Örnek, C bağımlılığını özellikle kullanır: uyumsuz C++ tanımlarını taşınabilir bir dil tekniği gibi sunmadan Mach-O dışa aktarma denetimini gösterir. Ana makinenin bağımlılığı 10 döndürür; eklentinin özel derlemesi ise 20 döndürür. Bu değerler örnek amaçlı girdilerdir.
Xcode Command Line Tools'un yüklü olduğu bir Mac'te, aşağıdaki blokları aynı Terminal kabuğunda sırayla çalıştırın. Yeni bir geçici dizin oluşturur ve etkin araç zincirinin varsayılan mimarisi için derleme yaparlar. Örnek incelendi ancak bu araştırma sırasında derlenip çalıştırılmadı; çünkü kullanılabilir ortam Apple araç zincirine sahip olmayan Linux'tu.
Bağımlılığı ve eklentiyi oluşturun
set -eu
example_dir=$(mktemp -d)
cd "$example_dir"
cat > dependency.h <<'EOF'
#ifndef DEMO_DEPENDENCY_H
#define DEMO_DEPENDENCY_H
#ifdef __cplusplus
extern "C" {
#endif
int demo_dependency_value(void);
#ifdef __cplusplus
}
#endif
#endif
EOF
cat > dependency.c <<'EOF'
#include "dependency.h"
#ifndef DEP_VALUE
#error DEP_VALUE must be supplied at compilation
#endif
int demo_dependency_value(void) {
return DEP_VALUE;
}
EOF
cat > plugin.cpp <<'EOF'
#include "dependency.h"
extern "C" __attribute__((visibility("default")))
int demo_plugin_value(void) {
return demo_dependency_value();
}
EOF
cat > exports.txt <<'EOF'
_demo_plugin_value
EOF
Bağımlılık başlığı, C++ sarmalayıcıya doğru C dili bağlantı bildirimini sağlar. Herkese açık sarmalayıcı da bu giriş noktasının bağlayıcı adını basit tutmak için C bağlantısı kullanır. C++ adları genellikle ad değişikliğine uğrar; Apple bu ayrımı Controlling Symbol Visibility belgesinde ele alır.
Ana makineyi oluşturun
Ana makine eklentiyi yükler ve döndürülen kitaplık tutamacı üzerinden herkese açık işlevini alır. Apple, dlopen desteğini hem dinamik kitaplıklar hem de Mach-O paketleri için belgeler. RTLD_LOCAL seçeneği, yüklenen görüntünün dışa aktarılan simgelerinin kullanılabilirliğini sınırlar; derleme zamanı görünürlük denetimlerinin yerini almaz. Apple'ın dlopen kılavuzuna bakın.
cat > host.cpp <<'EOF'
#include "dependency.h"
#include <cstdio>
#include <dlfcn.h>
int main() {
void* handle = dlopen("./DemoPlugin.dylib", RTLD_NOW | RTLD_LOCAL);
if (!handle) {
std::fprintf(stderr, "dlopen: %s\n", dlerror());
return 1;
}
dlerror();
void* address = dlsym(handle, "demo_plugin_value");
const char* error = dlerror();
if (error || !address) {
std::fprintf(stderr, "dlsym: %s\n",
error ? error : "null function address");
dlclose(handle);
return 1;
}
using PluginValue = int (*)(void);
PluginValue plugin_value = reinterpret_cast<PluginValue>(address);
const int host_result = demo_dependency_value();
const int plugin_result = plugin_value();
std::printf("host=%d plugin=%d\n", host_result, plugin_result);
const bool correct = host_result == 10 && plugin_result == 20;
if (dlclose(handle) != 0) {
std::fprintf(stderr, "dlclose: %s\n", dlerror());
return 1;
}
return correct ? 0 : 2;
}
EOF
Derleyin ve bağlayın
# Build the plug-in's dependency with hidden definitions.
xcrun clang -fvisibility=hidden -DDEP_VALUE=20 \
-c dependency.c -o plugin_dependency.o
xcrun ar rcs libPluginDependency.a plugin_dependency.o
# Compile the public wrapper, then constrain the final exports.
xcrun clang++ -std=c++17 -fvisibility=hidden \
-c plugin.cpp -o plugin.o
xcrun clang++ -dynamiclib plugin.o libPluginDependency.a \
-Wl,-exported_symbols_list,exports.txt \
-o DemoPlugin.dylib
# Give the host a separately compiled implementation.
xcrun clang -DDEP_VALUE=10 -c dependency.c -o host_dependency.o
xcrun clang++ -std=c++17 host.cpp host_dependency.o \
-Wl,-exported_symbol,_demo_dependency_value -o host
./host
Beklenen çıktı host=10 plugin=20 şeklindedir. Örnek, aynı C adına sahip harici olarak görülebilen bir uygulamayı da içersin diye ana makine bağımlılık simgesini açıkça dışa aktarır. Eklenti yine de kendi özel uygulamasını içermelidir.
Bu bir doğrulama örneğidir; denetimler kaldırıldığında sonucun mutlaka başarısız olacağı iddiası değildir. Varsayılan Mach-O bağlama davranışı birçok düzenlemede tanımları zaten ayırt edebilir. Yararlı denetimler, seçilen uygulamayı ve son dışa aktarma yüzeyini incelemektir.
Dışa aktarımları ve içe aktarımları ayrı ayrı doğrulayın
Örnek dizininde şu komutları çalıştırın:
xcrun nm -g -U -j DemoPlugin.dylib
xcrun dyld_info -exports DemoPlugin.dylib
xcrun dyld_info -imports DemoPlugin.dylib
xcrun otool -L DemoPlugin.dylib
İlk komutta -g dış simgeleri seçer, -U tanımsız simgeleri hariç tutar ve -j adları yazdırır. Bu basit eklenti için beklenen tanımlı dış ad _demo_plugin_value şeklindedir; _demo_dependency_value bulunmamalıdır. Apple'ın nm kılavuzu bu seçenekleri belgeler ve simge tablosu bilgilerinin yükleyicinin kullandığı bilgilerden farklı olabileceğini açıklar.
Yükleyiciye yönelik dışa aktarımları incelemek için dyld_info -exports, içe aktarılan simgeleri incelemek için -imports kullanın. Bağımlılık işlevi her iki listede de görünmemelidir: bu eklentinin içinde özel olarak uygulanmıştır. Bu inceleme seçenekleri Apple'ın dyld_info kılavuzunda belgelenmiştir.
Gerçek dylib bağımlılıklarını denetlemek için otool -L kullanın. Statik arşiv ayrı bir çalışma zamanı bağımlılığı olarak görünmemelidir. Apple bu bağımlılık incelemesini Dynamic Library Usage Guidelines belgesinde açıklar.
Gerçek bir eklenti için üç soruyu ayrı ayrı değerlendirin: Gerekli uygulama dahil edilmiş mi? Dışa aktarılıyor mu? Herhangi bir çağrı hâlâ rakip bir uygulamayı içe aktarıyor mu? Başarılı bir derleme tek başına bu soruların hiçbirini yanıtlamaz.
Aynı denetimleri Xcode'da uygulayın
Son eklenti ikili dosyasını üreten hedefte Exported Symbols File (EXPORTED_SYMBOLS_FILE) ayarını dışa aktarma listesinin projeye göreli yoluna ayarlayın. Özel uygulamayı derleyen C ve C++ hedeflerinde Symbols Hidden by Default (GCC_SYMBOLS_PRIVATE_EXTERN) ayarını etkinleştirin. Apple'ın güncel derleme ayarları başvurusu her iki ayarı da belgeler.
Bağımlılık ayrı bir statik kitaplık hedefiyse o hedefi de yapılandırın. Yalnızca eklentinin derleyici ayarlarını değiştirmek, önceden derlenmiş bağımlılık nesnelerini etkilemez. Etkilenen hedefleri yeniden derleyin ve ortaya çıkan ürünü dağıttığınız her mimari için inceleyin.
Önceden derlenmiş bir arşiv için son dışa aktarma listesi yine de yararlıdır. Apple'ın yayımladığı bağlayıcı kılavuzu, normal arşiv çıkarımını kullanırken arşivdeki genel simgeleri gizli olarak ele almak için -load_hidden seçeneğini de belgeler. Bu kılavuz ld-classic içindir; bu ek seçeneği belirlemeden önce yüklü bağlayıcının belgelerini kontrol edin. -force_load, arşiv üyelerinin dahil edilmesini denetler; bu ayrı bir konudur. Apple bağlayıcı kılavuzuna bakın.
C++ ve Swift arayüz gereksinimlerini koruyun
C++ görünürlüğü uyumsuz tanımları düzeltmez
C++ tek tanım kuralı, birden çok tanıma izin verilen durumlarda eşleşen belirteçler ve ad araması gereksinimleri de dahil olmak üzere, çeviri birimleri arasındaki tanımlara ilişkin koşullar getirir. Bir simgeyi gizlemek, çelişen C++ tanımlarını bir araya getirme izni vermez. Bağımsız olarak sürümlenen uygulamalar için mümkün olduğunda farklı ad alanları veya simge adları kullanın. C++ çalışma taslağındaki tek tanım kuralına bakın.
Ayrıca amaçlanan bir C++ arayüzü için gereken görünürlüğü koruyun. Apple, kitaplıklar arası RTTI, istisnalar, dinamik dönüştürmeler, satır içi işlevlerin adres kimliği ve paylaşılan statik nesnelerin dikkat gerektiren durumlar olduğunu belirtir. Kullanışlı bir tasarım, bağımlılık türlerini ve sahipliğini eklenti içinde tutup üzerinde uzlaşılmış skaler değerler veya opak tutamaçlar kullanan küçük bir arayüz sunmaktır. Controlling Symbol Visibility belgesine bakın.
Swift içe aktarma erişimi farklı bir sınırı denetler
Swift'teki internal import, içe aktarılan bildirimlere nerelerde başvurulabileceğini sınırlar. Swift 6.0'da uygulanan SE-0409, @_implementationOnly import kullanımlarının yerine internal import veya daha düşük erişim düzeyinde bir içe aktarma kullanılmasını önerir. Bu, Mach-O bağımlılıklarını yeniden adlandırma ya da dışa aktarımları filtreleme mekanizması tanımlamaz. Yerel ikili dosyaları ayrı olarak yapılandırıp incelemeye devam edin. İçe aktarma bildirimlerindeki erişim düzeyi belirteçleri belgesine bakın.
Doğrudan Swift/C++ birlikte çalışabilirliğini kullanıyorsanız araç zinciri kısıtlamalarını koruyun: destek Swift 5.9 ile başlar, karma kod aynı C++ standart kitaplığını kullanmalıdır ve Apple platformları libc++ kullanır. İçe aktarılan C++ başvuru türleri ayrıca macOS 13.3 veya üzeri bir en düşük dağıtım sürümü gerektirir. Dışa aktarma filtreleme bu gereksinimlerin hiçbirini değiştirmez. C++ Birlikte Çalışabilirliğinin Desteklenen Özellikleri ve Kısıtlamaları belgesine bakın.
Amaçlanan giriş noktaları kullanılabilir durumda kaldığında, özel bağımlılık tanımları yükleyici dışa aktarımlarında bulunmadığında ve içe aktarımlar eklentinin kullanması gereken kitaplıklarla eşleştiğinde eklenti entegrasyon testine hazırdır. Sonraki bağımlılık güncellemelerinin hem arayüz değişikliklerini hem de uygulama seçimi değişikliklerini ortaya çıkarması için bu denetimleri, örnekteki gibi davranışsal bir testle birlikte tutun.
Kaynaklar
- Derleme ayarları başvurusu — Apple.
- Dynamic Library Design Guidelines — Apple.
- Dynamic Library Usage Guidelines — Apple.
- Controlling Symbol Visibility — Apple.
- Clang komut satırı bağımsız değişkeni başvurusu — LLVM Project.
- ld-classic(1) kılavuzu — Apple.
- dlopen(3) kılavuzu — Apple.
- nm(1) kılavuzu — Apple.
- dyld_info(1) kılavuzu — Apple.
- Tek tanım kuralı — Tim Song tarafından yayımlanan C++ çalışma taslağı.
- İçe aktarma bildirimlerindeki erişim düzeyi belirteçleri — Swift Project.
- C++ Birlikte Çalışabilirliğinin Desteklenen Özellikleri ve Kısıtlamaları — Swift.org.