Atualizado: 20/11/2025

Este conteúdo é original e não foi gerado por inteligência artificial.

A Pilha no Início do Programa: Argumentos e Variáveis de Ambiente em Assembly NASM para Linux

Quando o Linux carrega um programa na memória, ele prepara uma área de memória chamada pilha (stack). No momento em que a primeira instrução do programa é executada (no nosso caso, no rótulo _start), essa pilha não está vazia. Ela já contém informações cruciais passadas pelo sistema operacional.

A estrutura da pilha, do endereço mais alto para o mais baixo, é a seguinte:

+----------------------------------------------------+
| Ponteiro Nulo (terminador do ambiente)             |
+----------------------------------------------------+
| Ponteiro para a n-ésima variável de ambiente       |
+----------------------------------------------------+
| ...                                                |
+----------------------------------------------------+
| Ponteiro para a primeira variável de ambiente      |
+----------------------------------------------------+
| Ponteiro Nulo (terminador dos argumentos)          |
+----------------------------------------------------+
| Ponteiro para o n-ésimo argumento da linha de comando |
+----------------------------------------------------+
| ...                                                |
+----------------------------------------------------+
| Ponteiro para o primeiro argumento (argv[1])       |
+----------------------------------------------------+
| Ponteiro para o nome do programa (argv[0])         |
+----------------------------------------------------+
| Número de argumentos da linha de comando (argc)    | <--- RSP aponta aqui
+----------------------------------------------------+

No ponto de entrada _start, o ponteiro da pilha RSP aponta diretamente para o número de argumentos da linha de comando (argc). Este valor inclui o nome do próprio programa, então ele será sempre no mínimo 1. Logo acima na pilha, encontramos uma sequência de ponteiros para as strings dos argumentos (argv), terminada por um ponteiro nulo. Acima disso, há uma sequência semelhante de ponteiros para as variáveis de ambiente.

Obtendo o Número de Argumentos

Podemos facilmente obter argc lendo o valor no topo da pilha.

global _start

section .text
_start:
    mov rdi, [rsp]  ; Obtém argc em RDI (será o código de saída)
    mov rax, 60
    syscall

Vamos compilar e testar com diferentes números de argumentos:

$ nasm -felf64 hello.asm -o hello.o
$ ld hello.o -o hello
$ ./hello
$ echo $?
1

Sem argumentos, o código de saída é 1 (apenas o nome do programa).

$ ./hello arg1 arg2 arg3
$ echo $?
4

Com três argumentos, o código de saída é 4 (3 argumentos + o nome do programa).

Imprimindo o Nome do Programa

Para imprimir uma string, precisamos de seu endereço e de seu tamanho. O endereço do nome do programa (argv[0]) está em [RSP+8]. Para encontrar o tamanho, precisamos localizar o byte nulo (\0) que a termina. A instrução repne scasb é perfeita para isso.

global _start

section .text
_start:
    ; 1. Encontra o tamanho da string
    mov rdi, [rsp+8]    ; RDI = endereço do nome do programa (argv[0])
    xor rax, rax        ; AL = 0 (o byte que estamos procurando)
    mov rcx, -1         ; Um número muito grande para o contador
    repne scasb         ; Procura pelo byte nulo

    ; Agora, RDI aponta para o byte *após* o nulo.
    ; A diferença entre o ponteiro final e o inicial é o tamanho.
    mov rdx, rdi        ; Salva o ponteiro final
    sub rdx, [rsp+8]    ; RDX = tamanho da string (incluindo o byte nulo)
    dec rdx             ; RDX = tamanho da string (excluindo o byte nulo)

    ; 2. Imprime a string
    mov rdi, 1          ; 1º parâmetro: stdout
    mov rsi, [rsp+8]    ; 2º parâmetro: endereço da string
    ; RDX já contém o tamanho
    mov rax, 1          ; syscall write
    syscall

    mov rax, 60
    syscall

Ao executar, o programa imprimirá como foi chamado:

$ ./hello
./hello

Lendo e Imprimindo Todos os Argumentos

Podemos iterar sobre a lista de ponteiros de argumentos para imprimir todos eles.

global _start

section .text
_start:
    mov rbx, 0          ; RBX = índice do argumento atual
    mov r15, [rsp]      ; R15 = argc
scan_args:
    ; Pega o endereço do argumento atual
    mov rdi, [rsp + 8 + rbx*8]

    ; Calcula o tamanho da string
    push rbx            ; Salva os registradores que serão modificados
    push rdi
    xor rax, rax
    mov rcx, -1
    repne scasb
    pop rsi             ; Recupera o endereço inicial em RSI
    pop rbx
    sub rdi, rsi        ; RDI = tamanho
    dec rdi

    ; Imprime o argumento
    mov rdx, rdi
    mov rdi, 1
    ; rsi já contém o endereço do argumento
    mov rax, 1
    syscall

    ; Imprime uma nova linha
    mov rax, 1
    mov rdi, 1
    lea rsi, [newline]
    mov rdx, 1
    syscall

    inc rbx             ; Próximo argumento
    cmp rbx, r15        ; Já processamos todos?
    jne scan_args       ; Se não, repete o laço

    mov rax, 60
    syscall

section .data
    newline: db 10

O programa percorre todos os ponteiros de argv[0] até argv[argc-1], calcula o tamanho de cada string e a imprime no console, seguida por uma quebra de linha.

Resultado da execução:

$ ./hello arg1 arg2 arg3
./hello
arg1
arg2
arg3

A mesma técnica pode ser aplicada para ler as variáveis de ambiente, que estão localizadas na pilha logo após a lista de argumentos.


Resumo

  • No ponto de entrada _start de um programa Linux, a pilha já contém o número de argumentos (argc), um array de ponteiros para os argumentos (argv), e um array de ponteiros para as variáveis de ambiente.
  • RSP aponta para argc.
  • [RSP+8] aponta para o nome do programa (argv[0]).
  • Os argumentos da linha de comando podem ser acessados iterando sobre os ponteiros em [RSP + 8 + índice*8].
  • A instrução repne scasb é uma forma eficiente de calcular o comprimento de strings terminadas com byte nulo.
Política de Privacidade

Copyright © www.programicio.com Todos os direitos reservados

É proibida a reprodução do conteúdo desta página sem autorização prévia do autor.

Contato: programicio@gmail.com