Como a alocação de pilha funciona no Linux?

17

O sistema operacional reserva a quantidade fixa de espaço virtual válido para a pilha ou outra coisa? Eu sou capaz de produzir um estouro de pilha apenas usando grandes variáveis locais?

Eu escrevi um pequeno programa C para testar minha suposição. Está rodando no X86-64 CentOS 6.5.

#include <string.h>
#include <stdio.h>
int main()
{
    int n = 10240 * 1024;
    char a[n];
    memset(a, 'x', n);
    printf("%x\n%x\n", &a[0], &a[n-1]);
    getchar();
    return 0;
}

A execução do programa fornece &a[0] = f0ceabe0 e &a[n-1] = f16eabdf

Os mapas de proc mostram a pilha: 7ffff0cea000-7ffff16ec000. (10248 * 1024B)

Depois tentei aumentar n = 11240 * 1024

A execução do programa fornece &a[0] = b6b36690 e &a[n-1] = b763068f

Os mapas de proc mostram a pilha: 7fffb6b35000-7fffb7633000. (11256 * 1024B)

ulimit -s imprime 10240 no meu PC.

Como você pode ver, em ambos os casos, o tamanho da pilha é maior do que o que ulimit -s oferece. E a pilha cresce com uma variável local maior. O topo da pilha é de alguma forma 3-5kB mais off &a[0] (AFAIK a zona vermelha é 128B).

Então, como este mapa de pilha é alocado?

    
por Amos 20.07.2014 / 11:48

3 respostas

14

Parece que o limite de memória da pilha não está alocado (de qualquer maneira, não poderia com pilha ilimitada). link diz:

The C language stack growth does an implicit mremap. If you want absolute guarantees and run close to the edge you MUST mmap your stack for the largest size you think you will need. For typical stack usage this does not matter much but it's a corner case if you really really care

No entanto, o mapeamento da pilha seria o objetivo de um compilador (se tiver uma opção para isso).

EDIT: Após alguns testes em uma máquina Debian x84_64, descobri que a pilha cresce sem nenhuma chamada de sistema (de acordo com strace ). Então, isso significa que o kernel cresce automaticamente (isso é o que o "implícito" significa acima), ou seja, sem mmap / mremap explícito do processo.

Foi muito difícil encontrar informações detalhadas para confirmar isso. Eu recomendo Compreendendo o gerenciador de memória virtual do Linux de Mel Gorman. Suponho que a resposta esteja na Seção 4.6.1 Manipulando uma falha de página , com a exceção "Região inválida, mas ao lado de uma região expansível como a pilha" e a ação correspondente "Expandir a região e alocar uma página". Veja também D.5.2 Expandindo o Stack .

Outras referências sobre o gerenciamento de memória do Linux (mas com quase nada sobre a pilha):

EDIT 2: Esta implementação tem uma desvantagem: nos casos de canto, uma colisão de heap de pilha pode não ser detectada, mesmo no caso em que a pilha seria maior que o limite! O motivo é que uma gravação em uma variável na pilha pode acabar na memória heap alocada, caso em que não há falha de página e o kernel não pode saber que a pilha precisou ser estendida. Veja meu exemplo na discussão Colisão silenciosa de pilha-pilha sob o GNU / Linux I iniciado na lista de ajuda do gcc. Para evitar isso, o compilador precisa adicionar algum código na chamada de função; isso pode ser feito com -fstack-check para o GCC (veja a resposta de Ian Lance Taylor e a página do GCC para detalhes).

    
por 20.07.2014 / 12:51
5

kernel do Linux 4.2

Programa de teste mínimo

Podemos, então, testá-lo com um programa mínimo de NASM de 64 bits:

global _start
_start:
    sub rsp, 0x7FF000
    mov [rsp], rax
    mov rax, 60
    mov rdi, 0
    syscall

Certifique-se de desativar o ASLR e remover as variáveis de ambiente, pois elas irão para a pilha e ocuparão espaço:

echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
env -i ./main.out

O limite está em algum lugar abaixo do meu ulimit -s (8MiB para mim). Parece que isso ocorre devido a dados extras do System V especificados inicialmente colocados na pilha além do ambiente: Parâmetros da linha de comandos do Linux 64 em Assembly | Estouro de pilha

Se você é sério sobre isso, TODO faça uma imagem initrd mínima que comece a escrever a partir do topo da pilha e desça e, em seguida, execute-o com o QEMU + GDB . Coloque um dprintf no loop imprimindo o endereço da pilha e um ponto de interrupção em acct_stack_growth . Será glorioso.

Relacionados:

por 28.10.2015 / 22:00
2

Por padrão, o tamanho máximo da pilha é configurado para 8MB por processo,
mas pode ser alterado usando ulimit :

Mostrando o padrão em kB:

$ ulimit -s
8192

Defina como ilimitado:

ulimit -s unlimited

afetando o shell e subshells atuais e seus processos filhos.
( ulimit é um comando interno do shell)

Você pode mostrar o intervalo real de endereços de pilha em uso com:
cat /proc/$PID/maps | grep -F '[stack]'
no Linux.

    
por 20.07.2014 / 12:06