---
id: 191ebec04cf97317ba418a120521d30b
author: Lee Yunjin
title: Análise da estrutura de tempo de execução de linguagens gerenciadas e de binários Go em assembly AMD64
description: Compara os modelos de execução de linguagens gerenciadas (managed) e não gerenciadas (unmanaged) e analisa, a partir do assembly AMD64 extraído com go tool objdump, a verificação de pilha (stack check) e a estrutura de chamadas de funções de runtime em binários Go.
language: pt
date: 2026-05-17T12:00:00Z
path: /blog/posts/managed-languages-intro-656b7470
category: lowlevel
---

## O que é uma linguagem gerenciada?

Uma linguagem gerenciada é aquela que, diferentemente das linguagens não gerenciadas, ou seja, linguagens que apenas executam a lógica escrita pelo programador sem desviar muito dela, executa no runtime tarefas como GC, otimizações em tempo de execução, green threads e tratamento de concorrência, eliminando a necessidade de o usuário realizar gerenciamento de baixo nível arriscado.
Nesse tipo de linguagem, há a vantagem de poder concentrar-se na lógica de negócio e mergulhar no desenvolvimento; por outro lado, o comportamento real do programa pode diferir da intuição do programador, o que, por vezes, exige ajustes finos de runtime.
Primeiramente, entre as linguagens gerenciadas, examinaremos a linguagem Go, que segue com maior fidelidade a filosofia minimalista e cujo assembly é transparente.

### Estrutura binária da linguagem Go

| .text              | .data         | .gopclntab, .typelink etc. |
| ------------------ | ------------- | -------------------------- |
| Código de máquina a ser executado | Dados a serem armazenados | Seções do runtime da linguagem |

Como a linguagem Go não traduz o código escrito pelo usuário para código de máquina de forma 1:1, a lógica da seção .text está intimamente relacionada às seções do runtime da linguagem.
Além disso, funções que o usuário não escreveu, como runtime.printnl(), são adicionadas ao assembly da seção .text.
Por meio dessa inserção automática de código, a linguagem Go auxilia o desenvolvedor a se libertar do gerenciamento manual.

### Observando apenas a função main em Go

Inicialmente, vamos escrever um código-fonte de exemplo simples, main.go, e observar a função main **em uma máquina AMD64**.

```go
package main

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

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

```

Em seguida, realiza-se a compilação com o seguinte comando.

```bash
go build main.go

```

Para facilitar a depuração de baixo nível, o Go oferece suporte ao go tool. Para visualizar apenas o assembly correspondente à função main do pacote main, insere-se o seguinte comando.

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

```

- Compara-se, com CMPQ, o valor do guarda de pilha presente no registrador de bloco de controle de goroutine (R14) com o ponteiro de pilha atual (SP), a fim de verificar se há espaço suficiente para o frame de pilha da goroutine; caso não haja, realiza-se um salto (JBE) para o endereço 0x468f95, o Entrypoint responsável pela expansão da pilha.
- O ponteiro de base anterior é salvo na pilha por meio de PUSHQ BP.
- O ponteiro de base (BP) recebe uma cópia do ponteiro de pilha atual (SP), fixando o ponto de referência da pilha no início da função.
- Em seguida, aloca-se um espaço de variáveis locais de 16 bytes (SUBQ $0x10, SP) e preenche-se com NOPL uma instrução virtual para realizar o alinhamento no cache da CPU.
- Para sincronizar a saída padrão de strings internas do runtime do Go, chama-se runtime.printlock(SB), adquirindo o lock.
- Com a instrução LEAQ, armazena-se no registrador AX, que segundo a convenção ABI do Go é usado como primeiro parâmetro, o endereço inicial da string constante ("Hello World").
- Em seguida, armazena-se no segundo registrador de parâmetro, BX, o valor que representa o comprimento da string (MOVL $0xb, BX, ou seja, 11 em decimal).
- Chama-se runtime.printstring(SB), que imprime no console com base nas informações recebidas em AX (endereço dos dados) e BX (comprimento).
- Para tratar a quebra de linha, chama-se runtime.printnl(SB).
- Como a saída foi concluída, libera-se o lock por meio de runtime.printunlock(SB).
- Restaura-se a memória de pilha de 16 bytes alocada por meio de ADDQ $0x10, SP.
- Restaura-se o ponteiro de base anterior com POPQ BP.
- Por meio de RET, devolve-se o controle ao ponto em que a função foi chamada.
- Caso o espaço tenha sido insuficiente na verificação inicial da pilha, chama-se runtime.morestack_noctxt.abi0(SB) no endereço 0x468f95, expandindo dinamicamente a pilha do runtime, como convém a uma linguagem gerenciada.
- Concluída a expansão da pilha, retorna-se (JMP) ao ponto de entrada de main.main(SB).
  Como se observa, o assembly da lógica de negócio é bastante claro e apenas acrescido de um gerenciamento leve do runtime.

### Quando não há otimização

A forma acima é o resultado de o compilador do Go ter aplicado automaticamente inlining às duas funções separadas. Entretanto, para fins de estudo, faremos com que sayHello não seja inlined neste caso.
Para isso, compila-se o código-fonte com a seguinte flag.

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

```

Ao exibir o resultado no shell, nota-se a presença de assembly duplicado.

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

```

Ao desativar o inlining, são inseridas operações MOVQ que recarregam os parâmetros (AX, BX) em deslocamentos da pilha, como 0x20(SP), para preservá-los conforme a convenção de chamada de funções.
Ou seja, confirma-se que a otimização do compilador visa justamente essas operações desnecessárias de movimentação de memória e o overhead de chamadas de função.

### Próxima aula

Na próxima aula, trataremos das instruções if e switch na linguagem Go. Se houver tempo no futuro, também pretendemos analisar as seções do runtime do Go.
