---
id: 191ebec04cf97317ba418a120521d30b
author: Lee Yunjin
title: Felügyelt nyelvek futásidejű szerkezete és a Go bináris AMD64 assembly elemzése
description: A menedzselt és a nem menedzselt nyelvek végrehajtási modelljeit hasonlítjuk össze, és a go tool objdump eszközzel kinyert AMD64 assembly kód segítségével elemezzük a Go bináris fájlok verem-ellenőrzését (stack check) és a futtatókörnyezeti függvényhívások szerkezetét.
language: hu
date: 2026-05-17T12:00:00Z
path: /blog/posts/managed-languages-intro-656b7470
category: lowlevel
---

## Mi az a menedzselt nyelv?

A menedzselt nyelv olyan nyelv, amely a nem menedzselt nyelvekkel ellentétben, amelyek esetében a programozó által megírt logikát lényegében csak végrehajtják, a szemétgyűjtést (GC), a futásidejű optimalizálást, a zöld szálakat, a konkurens feldolgozást és hasonló feladatokat a futásidejű környezetben végzi el, így a felhasználónak nem kell veszélyes, alacsony szintű erőforrás-kezelést végeznie.
Az ilyen nyelvek előnye, hogy a fejlesztő kizárólag az üzleti logikára koncentrálhat és teljesen elmerülhet a fejlesztésben, hátrányuk azonban, hogy a programozó elképzelése és a program tényleges működése eltérhet egymástól, ezért néha finomhangolt futásidejű beállításokra is szükség lehet.
Először a menedzselt nyelvek közül azt a Go nyelvet vizsgáljuk meg, amely a legszigorúbban követi a minimalista filozófiát és az assembly kódja a leginkább őszinte.

### A Go nyelv bináris szerkezete

| .text                | .data               | .gopclntab, .typelink stb. |
| -------------------- | ------------------- | -------------------------- |
| Végrehajtandó gépi kód | Tárolandó adatok  | Nyelvi futásidejű szekciók |

Mivel a Go nyelv nem fordítja le 1:1 arányban gépi kódra a felhasználó által megadott kódot, a .text szekció logikája szorosan összefügg a nyelvi futásidejű szekciókkal.
Emellett a felhasználó által nem írt függvények, például a runtime.printnl(), is bekerülnek a .text szekció assembly kódjába.
Ezen automatikus kódbeszúrás révén a Go nyelv segít abban, hogy a fejlesztő megszabaduljon a kézi erőforrás-kezeléstől.

### Csak a main függvény vizsgálata Go-ban

Először írjunk egy egyszerű, main.go nevű példaforrást, és nézzük meg a main függvényt **AMD64 gépen**.

```go
package main

func sayHello(msg string) {
    println(msg)
}

func main() {
    sayHello("Hello World")
}

```

Ezután fordítsuk le a következő paranccsal.

```bash
go build main.go

```

A Go az egyszerű alacsony szintű hibakereséshez a go tool eszközt biztosítja.
A go toolban a főcsomag main függvényének megfelelő assembly kódrészlet megtekintéséhez a következő parancsot adjuk ki.

```bash
go tool objdump -s "main\.main" ./main

```

### Assembly

```go
TEXT main.main(SB) /home/yjlee/compare-assembly/go/main.go
  main.go:7             0x468f60                493b6610                CMPQ SP, 0x10(R14)
  main.go:7             0x468f64                762f                    JBE 0x468f95
  main.go:7             0x468f66                55                      PUSHQ BP
  main.go:7             0x468f67                4889e5                  MOVQ SP, BP
  main.go:7             0x468f6a                4883ec10                SUBQ $0x10, SP
  main.go:8             0x468f6e                90                      NOPL
  main.go:4             0x468f6f                e8cca3fcff              CALL runtime.printlock(SB)
  main.go:4             0x468f74                488d05da290100          LEAQ 0x129da(IP), AX
  main.go:4             0x468f7b                bb0b000000              MOVL $0xb, BX
  main.go:4             0x468f80                e83bacfcff              CALL runtime.printstring(SB)
  main.go:4             0x468f85                e8f6a5fcff              CALL runtime.printnl(SB)
  main.go:4             0x468f8a                e811a4fcff              CALL runtime.printunlock(SB)
  main.go:9             0x468f8f                4883c410                ADDQ $0x10, SP
  main.go:9             0x468f93                5d                      POPQ BP
  main.go:9             0x468f94                c3                      RET
  main.go:7             0x468f95                e8e6afffff              CALL runtime.morestack_noctxt.abi0(SB)
  main.go:7             0x468f9a                ebc4                    JMP main.main(SB)

```

- A CMPQ utasítással összehasonlítjuk a jelenlegi goroutine veremkeretének elegendő méretét a goroutine vezérlőblokk regiszterében (R14) lévő veremőr értékkel és a jelenlegi veremmutatóval (SP). Ha a hely nem elegendő, a JBE utasítással a 0x468f95 címre ugrunk, amely a verem bővítésének belépési pontja.
- A PUSHQ BP utasítással elmentjük a korábbi bázismutatót a verembe.
- A MOVQ SP, BP utasítással a bázismutató (BP) regiszterbe másoljuk a jelenlegi veremmutatót, így rögzítjük a függvény indulásakor érvényes veremalapot.
- Ezt követően 16 bájtnyi lokális változó-területet foglalunk le (SUBQ $0x10, SP), a NOPL utasítással pedig virtuális utasításokkal töltjük ki a helyet a CPU-gyorsítótár-igazítás érdekében.
- A Go futásidejű környezetében a belső karakterláncok szabványos kimenetének szinkronizálása érdekében a runtime.printlock(SB) meghívásával zárat helyezünk el.
- A LEAQ utasítással a konstansként megadott karakterlánc ("Hello World") kezdőcímét a Go ABI szabványa szerint az első paraméterül szolgáló AX általános célú regiszterbe töltjük.
- Ezután a karakterlánc hosszát jelölő értéket a második paraméterregiszterbe, BX-be írjuk. (MOVL $0xb, BX, azaz tízes számrendszerben 11)
- A runtime.printstring(SB) meghívásával az átadott AX (adatcím) és BX (hossz) információk alapján kiíratjuk a konzolra a szöveget.
- A sortörés kezelése érdekében meghívjuk a runtime.printnl(SB) függvényt.
- A kiírás befejeződött, ezért a runtime.printunlock(SB) hívással feloldjuk a zárat.
- Az ADDQ $0x10, SP utasítással visszaállítjuk a 16 bájtnyi lefoglalt veremterületet.
- A POPQ BP utasítással visszaállítjuk a korábbi bázismutatót.
- A RET utasítással a vezérlést visszaadjuk a függvényt hívó helyre.
- Ha az első veremellenőrzésnél nem volt elegendő hely, a 0x468f95 címen lévő runtime.morestack_noctxt.abi0(SB) függvényt hívjuk meg, amely a menedzselt nyelvekhez illő módon dinamikusan bővíti a verem futásidejű környezetét.
- A verem bővítésének befejezése után a JMP utasítással visszatérünk a main.main(SB) belépési pontjára.
  Amint látható, az üzleti logika assembly kódja meglehetősen egyértelmű, és csupán vékony futásidejű erőforrás-kezelés egészül ki vele.

### Optimalizálás nélkül

A fenti forma annak az eredménye, hogy a Go fordító automatikusan beágyazta (inline) a két különálló függvényt. Mivel azonban a tanulás céljából nem szeretnénk, hogy ebben az esetben a sayHello függvény beágyazásra kerüljön, a forrást a következő kapcsolóval fordítjuk le.

```bash
go build -gcflags="-l" main.go

```

A terminálon az eredményt megvizsgálva ismétlődő assembly kódrészleteket találunk.

```bash
yjlee@elegant:~/compare-assembly/go$ go build -gcflags="-l" main.go

go tool objdump -s "main\.sayHello" ./main
TEXT main.sayHello(SB) /home/yjlee/compare-assembly/go/main.go
  main.go:3             0x468f60                493b6610               CMPQ SP, 0x10(R14)
  main.go:3             0x468f64                7636                   JBE 0x468f9c
  main.go:3             0x468f66                55                     PUSHQ BP
  main.go:3             0x468f67                4889e5                 MOVQ SP, BP
  main.go:3             0x468f6a                4883ec10               SUBQ $0x10, SP
  main.go:5             0x468f6e                4889442420             MOVQ AX, 0x20(SP)
  main.go:5             0x468f73                48895c2428             MOVQ BX, 0x28(SP)
  main.go:4             0x468f78                e8c3a3fcff             CALL runtime.printlock(SB)
  main.go:4             0x468f7d                488b442420             MOVQ 0x20(SP), AX
  main.go:4             0x468f82                488b5c2428             MOVQ 0x28(SP), BX
  main.go:4             0x468f87                e834acfcff             CALL runtime.printstring(SB)
  main.go:4             0x468f8c                e8efa5fcff             CALL runtime.printnl(SB)
  main.go:4             0x468f91                e80aa4fcff             CALL runtime.printunlock(SB)
  main.go:5             0x468f96                4883c410               ADDQ $0x10, SP
  main.go:5             0x468f9a                5d                     POPQ BP
  main.go:5             0x468f9b                c3                     RET
  main.go:3             0x468f9c                4889442408             MOVQ AX, 0x8(SP)
  main.go:3             0x468fa1                48895c2410             MOVQ BX, 0x10(SP)
  main.go:3             0x468fa6                e8d5afffff             CALL runtime.morestack_noctxt.abi0(SB)
  main.go:3             0x468fab                488b442408             MOVQ 0x8(SP), AX
  main.go:3             0x468fb0                488b5c2410             MOVQ 0x10(SP), BX
  main.go:3             0x468fb5                eba9                   JMP main.sayHello(SB)

```

Ha kikapcsoljuk a beágyazást, a függvényhívási konvenciónak megfelelően a paraméterek (AX, BX) megőrzése érdekében olyan MOVQ műveletek kerülnek be, amelyek a 0x20(SP) stackmutató-eltolású helyekre töltik vissza az értékeket.
Vagyis megállapítható, hogy a fordító optimalizációja éppen ezeknek a felesleges memóriamozgatási műveleteknek és hívási többletterhelésnek a kiküszöbölésére irányul.

### Következő alkalom

A következő alkalommal a Go nyelv if és switch utasításaival foglalkozunk. Ha később marad rá idő, a Go futásidejű szekcióit is elemezni fogjuk.
