یکی از بزرگترین نقاط قوت Go کامپایلر آن است. خیلی چیزها را برای شما خلاصه می کند و به شما امکان می دهد برنامه خود را به راحتی برای تقریباً هر پلتفرم و معماری کامپایل کنید.

و اگرچه آسان به نظر می رسد، اما برخی تفاوت های ظریف در آن و راه های متعددی برای کامپایل کردن یک برنامه وجود دارد که منجر به اجراهای متفاوت می شود.

در این مقاله، فایل‌های اجرایی مرتبط استاتیک و پویا، لینک‌کننده‌های داخلی و خارجی را بررسی می‌کنیم و باینری‌ها را با استفاده از ابزارهایی مانند فایل، ld، و ldd.

در اینجا چیزی است که ما پوشش خواهیم داد:

  • نمای کلی

  • پیوند استاتیک و دینامیک چیست؟

  • برنامه ایستا مرتبط

  • به هر حال باینری چیست؟

  • برنامه با پیوند پویا

  • آیا می توانیم آن را به صورت ایستا پیوند دهیم؟

  • لینک دهنده داخلی در مقابل خارجی

  • کامپایل متقابل

  • امتیاز امتیاز: کاهش اندازه باینری

  • مراقب باشید: ترفند LD_PRELOAD

  • نتیجه گیری

  • ادامه مطلب

پیوند استاتیک و دینامیک چیست؟

پیوند استاتیک تمرین کپی کردن تمام کتابخانه های مورد نیاز برنامه شما به طور مستقیم در تصویر فایل اجرایی نهایی است.

و برو دوست دارد و می خواهد که هر وقت ممکن باشد این به این دلیل است که قابل حمل تر است، زیرا نیازی به حضور کتابخانه ندارد روی را host سیستمی که در آن اجرا می شود. بنابراین باینری شما می تواند اجرا شود روی هر سیستمی بدون توجه به کدام توزیع/نسخه، و به آن بستگی ندارد روی هر کتابخانه سیستمی

پیوند پویا، روی از سوی دیگر، زمانی است که کتابخانه های خارجی یا اشتراکی در فایل اجرایی کپی می شوند با نام در طول زمان اجرا.

و مزایای خاص خود را نیز دارد. به عنوان مثال این برنامه می تواند دوباره از محبوب استفاده کند libc کتابخانه های موجود روی را host سیستم و عدم اجرای مجدد آنها. شما نیز می توانید بهره مند شوید host به روز رسانی بدون پیوند مجدد برنامه شما. همچنین در بسیاری از موارد می تواند حجم فایل اجرایی را کاهش دهد.

برنامه ایستا مرتبط

بیایید برنامه ای را بررسی کنیم که این کار را انجام دهد همیشه به صورت ایستا مرتبط شوید این برنامه با استفاده از کد C تماس نمی گیرد cgo، بنابراین همه چیز را می توان در یک باینری ثابت بسته بندی کرد. برنامه ما فقط یک پیام ساده برای stdout چاپ می‌کند، که Go می‌تواند آن را به صورت داخلی بدون نیاز به استفاده از چیزی از آن انجام دهد libc.

package main

import "fmt"

func main() {
    fmt.Println("hi, user")
}

به هر حال باینری چیست؟

می توانیم از a استفاده کنیم فایل برنامه ای که ابتدا نوع فایل را بررسی می کند.

$ go build main1.go

$ file main1 | tr , '\n'
main1: ELF 64-bit LSB executable
 ARM aarch64
 version 1 (SYSV)
 statically linked
 Go BuildID=...
 with debug_info
 not stripped

به ما می گوید که این یک ELF (قالب اجرایی و لینک پذیر) فایل اجرایی. همچنین به ما می گوید که “به صورت ایستا مرتبط است”.

پیشنهاد می‌کنیم بخوانید:  حذف عنصر از یک آرایه در جاوا اسکریپت در جاوا اسکریپت، و درست مانند بسیاری از زبان های دیگر، در برخی مواقع به احتمال زیاد نیاز به حذف یک عنصر از آرایه خواهید داشت. بسته به روی مورد استفاده شما این می تواند به آسانی استفاده از دستورات داخلی ()shift یا pop() باشد، اما فقط در صورتی کار می کند که عنصر در ...

ما به این نخواهیم پرداخت که ELF چیست، اما فرمت‌های فایل اجرایی دیگری نیز وجود دارد. ELF پیش فرض است روی لینوکس، Mach-O پیش فرض برای macOS، PE/PE32+ برای ویندوز و غیره است روی.

توجه: در این مقاله با لینوکس (اوبونتو) و ابزارسازی آن کار خواهیم کرد، اما همین امکان وجود دارد روی دیگر پلتفرم ها

و یک برنامه لینوکس دیگری به نام وجود دارد ldd که می تواند به ما بگوید که آیا باینری به صورت ایستا یا پویا به هم مرتبط است.

$ ldd main1
not a dynamic executable

برنامه با پیوند پویا

همانطور که در بالا ذکر شد، Go مکانیزمی به نام دارد cgo برای تماس با کد C از Go. حتی stdlib Go از آن در چندین مکان استفاده می کند – به عنوان مثال در خالص بسته، جایی که از کتابخانه استاندارد C برای کار با DNS استفاده می کند.

وارد کردن چنین بسته‌هایی یا استفاده از cgo در کدتان به‌طور پیش‌فرض، یک باینری با پیوند پویا تولید می‌کند که به آن‌ها مرتبط است. libc کتابخانه ها

package main

import (
    "fmt"
    "log"
    "net"
)

func main() {
    ipv4Addr, ipv4Net, err := net.ParseCIDR("192.0.2.1/24")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(ipv4Addr)
    fmt.Println(ipv4Net)
}

ما می توانیم از خود استفاده کنیم فایل و ldd دوباره برای بررسی باینری دوم برنامه می‌دهد.

$ go build main2.go

$ file main2 | tr , '\n'
main2: ELF 64-bit LSB executable
 ARM aarch64
 version 1 (SYSV)
 dynamically linked
 interpreter /lib/ld-linux-aarch64.so.1
 Go BuildID=...
 with debug_info
 not stripped

$ ldd main2
    linux-vdso.so.1 (0x0000ffff87c81000)
    libc.so.6 => /lib/aarch64-linux-gnu/libc.so.6 (0x0000ffff87a80000)
    /lib/ld-linux-aarch64.so.1 (0x0000ffff87c44000)

این فایل برنامه اکنون به ما نشان می دهد که یک است به صورت پویا دوست داشت باینری و ldd وابستگی های پویا باینری ما را به ما نشان می دهد. در این مورد متکی است روی libc.so.6 و ld-linux که یک لینکر پویا برای سیستم های لینوکس است.

آیا می توانیم آن را به صورت ایستا مرتبط کنیم؟

دلایل متعددی وجود دارد که ممکن است بخواهید باینری های خود ثابت باشند، اما اصلی ترین دلیل این است که استقرار و توزیع را آسان تر کنید. اما! همیشه لازم نیست و با پیوند دادن libc شما از host به روز رسانی ها همچنین، در مورد ما خالص بسته، از توابع پیچیده جستجوی DNS موجود در آن استفاده می کنید libc.

نکته جالب این است که بسته نت Go نیز دارای نسخه Pure-Go است که امکان غیرفعال کردن cgo را در زمان کامپایل فراهم می کند. شما می توانید این کار را با تعیین تگ های ساخت یا با غیرفعال کردن کامل cgo استفاده کنید CGO_ENABLED=0.

$ go build -tags netgo main2.go
$ ldd main2
not a dynamic executable

$ CGO_ENABLED=0 go build main2.go
$ ldd main2
not a dynamic executable

موارد بالا ثابت می کند که در هر دو مورد به یک باینری ثابت می رسیم.

لینکر داخلی در مقابل خارجی

Linker برنامه ای است که آرشیو یا شی Go را برای یک بسته اصلی همراه با وابستگی های آن می خواند و آنها را در یک باینری اجرایی ترکیب می کند.

به طور پیش فرض، Toolchain Go از پیوند دهنده داخلی خود (پیوند ابزار go) استفاده می کند، اما شما می توانید تعیین کنید که در طول زمان کامپایل از کدام لینک کننده استفاده کنید. این می تواند ترکیبی از مزایای یک باینری استاتیک و همچنین قابلیت های کامل libc را به شما بدهد.

در لینوکس، پیوند دهنده پیش فرض است gcc’s ld. و می توانیم به آن بگوییم که یک باینری ایستا تولید کند.

$ go build -ldflags "-linkmode 'external' -extldflags '-static'" main2.go
# command-line-arguments
/usr/bin/ld: /tmp/go-link-629224677/000004.o: in function `_cgo_97ab22c4dc7b_C2func_getaddrinfo':
/tmp/go-build/cgo_unix_cgo.cgo2.c:60:(.text+0x30):
warning: Using 'getaddrinfo' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
$ ldd main2
not a dynamic executable

این کار می کند، اما ما یک هشدار در اینجا داریم. در مورد ما glibc استفاده می کند لیبنس برای پشتیبانی از تعدادی ارائه دهنده مختلف برای خدمات حل آدرس و شما نمی توانید libnss را به صورت ایستا پیوند دهید.

پیشنهاد می‌کنیم بخوانید:  چگونه در هند بیت کوین بخریم و بفروشیم؟

سایر بسته‌های cgo ممکن است اخطارهای مشابهی ایجاد کنند و شما باید اسناد را بررسی کنید تا ببینید آیا آنها مهم هستند یا نه.

جمع آوری متقابل

همانطور که در مقدمه ذکر شد، کامپایل متقابل یکی از ویژگی های بسیار خوب Go است. این به شما امکان می دهد برنامه خود را برای تقریباً هر پلتفرم/معماری کامپایل کنید. اما اگر برنامه شما از آن استفاده کند می تواند بسیار مشکل باشد cgo، زیرا به طور کلی کامپایل کردن کد C دشوار است.

$ CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build main2.go
$ CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 go build main2.go
# runtime/cgo
cgo: C compiler "clang" not found: exec: "clang":
executable file not found in $PATH

شما می توانید با نصب زنجیره ابزار برای سیستم عامل و/یا معماری هدف بر آن غلبه کنید.

اگر می توانید، همیشه بهتر است از آن استفاده نکنید cgo برای کامپایل متقابل باینری های پایداری دریافت خواهید کرد که به صورت ایستا به هم مرتبط هستند.

امتیاز امتیاز: کاهش اندازه باینری

همانطور که ممکن است متوجه شوید، خروجی از فایل دستور بالا به شرح زیر بود: “with debug_info un stripped”. این بدان معناست که باینری ما اطلاعات اشکال زدایی را در خود دارد. اما معمولاً به آن نیازی نداریم و حذف آن ممکن است اندازه باینری را کاهش دهد.

$ go build main1.go
$ du -sh main1
1.9M    main1

$ go build -ldflags="-w -s" main1.go
$ du -sh main1
1.3M    main1

$ file main1 | tr , '\n'
main1: ELF 64-bit LSB executable
 ARM aarch64
 version 1 (SYSV)
 statically linked
 Go BuildID=...
 stripped

مراقب باشید: ترفند LD_PRELOAD

برنامه سیستم لینوکس ld-linux.so (لینکر/لودر پویا) استفاده می کند LD_PRELOAD برای بارگیری کتابخانه های مشترک مشخص شده به ویژه، قبل از هر کتابخانه دیگری، بارگذار پویا ابتدا کتابخانه های مشترکی را که در LD_PRELOAD هستند بارگیری می کند.

ترفند LD_PRELOAD یک تکنیک قدرتمند است که در باینری های پیوند شده پویا برای لغو یا رهگیری فراخوانی های تابع به کتابخانه های مشترک استفاده می شود.

با تنظیم متغیر محیطی LD_PRELOAD برای اشاره به یک فایل شی مشترک سفارشی، کاربران می توانند کد خود را به اجرای برنامه تزریق کنند و به طور موثر توابع کتابخانه موجود را جایگزین یا تقویت کنند.

این روش به برنامه های مختلفی مانند اشکال زدایی، آزمایش و حتی اصلاح رفتار برنامه بدون تغییر کد منبع اصلی اجازه می دهد.

LD_PRELOAD=/path/to/my/malloc.so /bin/ls

این را نیز نشان می دهد باینری های متصل به استاتیک امن تر هستند، زیرا آنها این مشکل را ندارند زیرا آنها به دنبال کتابخانه های خارجی نیستند. همچنین، یک “حالت اجرای امن” – یک ویژگی امنیتی که توسط پیوند دهنده پویا پیاده سازی شده است روی سیستم های لینوکس برای محدود کردن رفتارهای خاص هنگام اجرای برنامه هایی که نیاز به امتیازات بالا دارند.

نتیجه گیری

کامپیوترها جادو نیستند، فقط باید آنها را درک کنید.

و درک فرآیندهای کامپایل و اجرای Go برای توسعه برنامه های کاربردی چند پلتفرمی قوی بسیار مهم است.

امیدواریم پس از خواندن این مقاله، اکنون درک بهتری از روش کار کامپایل Go داشته باشید.

ادامه مطلب

  • مقالات بیشتری را از packagemain.tech کاوش کنید

  • Source کد

  • src/cmd/cgo/doc.go

  • cmd/لینک

  • اشکال زدایی یک خطای عجیب «فایل یافت نشد».

  • چگونه می توانیم به main() برسیم

  • مروری کلی بر آنچه قبل از main() اتفاق می افتد

  • زنگ قبل از اصلی