---
id: 191ebec04cf97317ba418a120521d30b
author: Lee Yunjin
title: Runtimestruktur for administrerede sprog og analyse af Go-binærfiler i AMD64-assembler
description: Denne analyse sammenligner eksekveringsmodellerne for administrerede (managed) og ikke-administrerede (unmanaged) sprog og analyserer, gennem AMD64-assembler udtrukket med go tool objdump, stakkontrollen og strukturen for runtime-funktionskald i Go-binære filer.
language: da
date: 2026-05-17T12:00:00Z
path: /blog/posts/managed-languages-intro-656b7470
category: lowlevel
---

## Hvad er et managed sprog?

Et managed sprog er et sprog, der i modsætning til et unmanaged sprog, altså et sprog der udfører programmørens logik uden at afvige meget fra den, hjælper brugeren med at undgå farlig lavniveau-håndtering ved at udføre GC, runtime-optimering, grønne tråde, samtidighedshåndtering og lignende i runtime.
Ved sådanne sprog er der den fordel, at man kan koncentrere sig om forretningslogikken og fordybe sig i udviklingen, men på den anden side kan programmets faktiske adfærd afvige fra programmørens intuition, så der somme tider er behov for præcis runtime-tuning.
Lad os først se på Go, som er det managed sprog, der er mest tro mod en minimalistisk filosofi, og hvor assembly er ærligt.

### Go-sprogets binære struktur

| .text              | .data         | .gopclntab, .typelink osv. |
| ------------------ | ------------- | -------------------------- |
| Maskinkode der skal køres | Data der skal gemmes | Sektioner for sprogets runtime |

Da Go ikke oversætter til maskinkode 1:1 ud fra det, brugeren har skrevet, er logikken i .text-sektionen tæt forbundet med sprogets runtime-sektioner.
Desuden tilføjes funktioner, som brugeren ikke selv har skrevet, såsom runtime.printnl(), til assembly i .text-sektionen.
Gennem denne automatiske kodeindsættelse hjælper Go med at frigøre udvikleren fra manuel håndtering.

### Kun main-funktionen i Go

Lad os først skrive et simpelt eksempelprogram main.go og se på main fra **en AMD64-maskine**.

```go
package main

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

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

```

Derefter bygger vi således.

```bash
go build main.go

```

Go understøtter go tool til nem lavniveau-fejlfinding.
For kun at se så meget assembly som main-funktionen i main-pakken indeholder, indtaster man denne kommando.

```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)

```

- Der kontrolleres, om den nuværende goroutine-stackramme har tilstrækkelig plads, ved at sammenligne stackvagtværdien i goroutinens kontrolblokregister (R14) med den aktuelle stackpointer (SP) via CMPQ; hvis pladsen er utilstrækkelig, hoppes (JBE) til indgangspunktet 0x468f95 for stackudvidelse.
- PUSHQ BP gemmer den forrige basispointer på stakken.
- Den aktuelle stackpointer (SP) kopieres til basispointer-registret (BP), så stackens referencepunkt ved funktionens start fastholdes.
- Derefter allokeres 16 bytes lokalt variabelstakrum (SUBQ $0x10, SP), og NOPL bruges til at udfylde med virtuelle instruktioner for at udføre CPU-cache-justering.
- Go-runtime kalder runtime.printlock(SB) for at tage låsen og synkronisere standardoutput for strenge.
- LEAQ-instruktionen bruges til at gemme startadressen for den konstante streng ("Hello World") i AX, som ifølge Go ABI-specifikationen er det første parameterregister.
- Derefter gemmes strengens længde, som repræsenterer 11 i decimal, i BX, det andet parameterregister (MOVL $0xb, BX).
- runtime.printstring(SB) kaldes for at udskrive til konsollen på baggrund af de overførte AX (dataadresse) og BX (længde).
- runtime.printnl(SB) kaldes for at håndtere linjeskift.
- Da udskriften er færdig, frigives låsen via runtime.printunlock(SB).
- ADDQ $0x10, SP gendanner de 16 bytes stakhukommelse, der blev allokeret.
- POPQ BP gendanner den oprindelige basispointer.
- RET returnerer kontrollen til det sted, hvor funktionen blev kaldt.
- Hvis stakkontrollen i starten viste, at pladsen var utilstrækkelig, kaldes runtime.morestack_noctxt.abi0(SB) på adressen 0x468f95, så stack-runtime udvider stakken dynamisk, som det sker i managed sprog.
- Når stakudvidelsen er færdig, vender programmet tilbage til indgangspunktet for main.main(SB) (JMP).
  Som det fremgår, er assembly for forretningslogikken ret tydelig og har kun et tyndt lag af runtime-håndtering tilføjet.

### Når der ikke er optimering

Ovenstående form er resultatet af, at Go-compileren automatisk inliner de to adskilte funktioner og optimerer dem. Til læringsformål vil vi dog i dette tilfælde ikke lade sayHello blive inlinet.
For at opnå dette kompilerer vi kildekoden med følgende flag.

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

```

Hvis man udskriver resultatet i shellen, findes der duplikeret assembly.

```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)

```

Når inlining deaktiveres, indsættes MOVQ-instruktioner, som gemmer parametrene (AX, BX) igen i stakken via stakpointer-forskydninger såsom 0x20(SP), for at bevare parametrene i overensstemmelse med funktionskaldskonventionen.
Det er dermed blevet bekræftet, at det, compileren optimerer, netop er disse unødvendige hukommelsesoverførsler og kaldoverhead.

### Næste gang

Næste gang vil vi behandle if- og switch-sætninger i Go. Hvis der senere er tid, vil vi også analysere Go-runtime-sektionerne.
