Linux에서 스택 할당은 어떻게 작동합니까?


18

OS가 스택 또는 다른 것에 대해 고정 된 양의 유효한 가상 공간을 예약합니까? 큰 지역 변수를 사용하여 스택 오버플로를 생성 할 수 있습니까?

C내 가정을 테스트하기 위해 작은 프로그램을 작성했습니다 . 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[0] = f0ceabe0&a[n-1] = f16eabdf

proc 맵은 스택을 보여줍니다 : 7ffff0cea000-7ffff16ec000. (10248 * 1024B)

그런 다음 증가하려고 n = 11240 * 1024

이 프로그램을 실행하면 제공 &a[0] = b6b36690&a[n-1] = b763068f

proc 맵은 스택을 보여줍니다 : 7fffb6b35000-7fffb7633000. (11256 * 1024B)

ulimit -s10240내 PC에 인쇄합니다 .

보시다시피, 두 경우 모두 스택 크기가 ulimit -s제공 하는 것보다 큽니다 . 스택은 더 큰 지역 변수로 커집니다. 스택 상단은 3 ~ 5kB 더 많이 떨어져 있습니다 &a[0](빨간색 영역은 128B입니다).

이 스택 맵은 어떻게 할당됩니까?

답변:


14

스택 메모리 제한이 할당되지 않은 것 같습니다 (어쨌든 무제한 스택으로는 불가능했습니다). https://www.kernel.org/doc/Documentation/vm/overcommit-accounting의 말 :

C 언어 스택 증가는 암시 적 mremap을 수행합니다. 절대적인 보장을 원하고 가장자리에 가까이 다가 가려면 필요한 최대 크기로 스택을 축소해야합니다. 일반적인 스택 사용의 경우 이것은 중요하지 않지만 실제로 관심이 있다면 코너 케이스입니다.

그러나 스택을 축소하는 것은 컴파일러의 목표입니다 (옵션이있는 경우).

편집 : x84_64 데비안 컴퓨터에서 몇 가지 테스트를 한 결과 시스템 호출없이 스택이 커짐을 알았습니다 (에 따라 strace). 따라서 이것은 커널이 자동으로 자라는 것을 의미합니다 (이것은 "암시 적"이 위에서 의미하는 것입니다). 즉 프로세스에서 명시 적 mmap/ mremap프로세스 없이 .

이를 확인하는 자세한 정보를 찾기가 매우 어려웠습니다. Mel Gorman 의 Linux 가상 메모리 관리자 이해를 권장 합니다. "영역이 유효하지 않지만 스택과 같은 확장 가능 영역 옆에 있음"과 해당 조치 "영역 확장 및 페이지 할당"을 제외 하고 섹션 4.6.1 페이지 결함 처리 에 대한 답변이 있다고 가정합니다 . D.5.2 스택 확장을 참조하십시오 .

Linux 메모리 관리에 대한 다른 참조 (그러나 스택에 대해서는 거의 아무것도 없음) :

편집 2 :이 구현에는 단점이 있습니다. 모퉁이의 경우 스택이 제한보다 큰 경우에도 스택 힙 충돌이 감지되지 않을 수 있습니다! 그 이유는 스택의 변수 쓰기가 할당 된 힙 메모리에서 종료 될 수 있으며,이 경우 페이지 결함이없고 커널은 스택을 확장해야한다는 것을 알 수 없기 때문입니다. gcc-help 목록에서 시작한 GNU / Linux 에서의 Silent stack-heap 충돌 에 대한 토론의 예를 참조하십시오 . 이를 피하기 위해 컴파일러는 함수 호출시 코드를 추가해야합니다. 이 작업 -fstack-check은 GCC를 통해 수행 할 수 있습니다 (자세한 내용은 Ian Lance Taylor의 회신 및 GCC 매뉴얼 페이지 참조).


내 질문에 대한 정답 인 것 같습니다. 그러나 그것은 나를 더 혼란스럽게합니다. mremap 호출은 언제 트리거됩니까? 프로그램에 내장 된 syscall입니까?
Amos

@amos 함수 호출이 필요하거나 alloca ()가 호출되면 mremap 호출이 트리거된다고 가정합니다.
vinc17

mmap이 무엇인지 모르는 사람들에게 언급하는 것이 좋습니다.
Faheem Mitha

@FaheemMitha 정보를 추가했습니다. mmap이 무엇인지 모르는 사용자는 위에서 언급 한 메모리 FAQ를 참조하십시오. 여기서 스택의 경우, "익명 매핑"이되어 사용되지 않은 공간은 실제 메모리를 사용하지 않지만 Mel Gorman이 설명했듯이 커널은 매핑 (가상 메모리)과 물리적 할당을 동시에 수행합니다. .
vinc17

1
@max ulimit -sOP의 조건에서와 같이 10240 을 제공 하여 OP의 프로그램을 시도했는데 예상대로 SIGSEGV를 얻습니다 (POSIX에 필요한 것 : "이 한계를 초과하면 스레드에 대해 SIGSEGV가 생성됩니다). "). OP의 커널에 버그가 있다고 생각합니다.
vinc17

6

리눅스 커널 4.2

최소 테스트 프로그램

그런 다음 최소 NASM 64 비트 프로그램으로 테스트 할 수 있습니다.

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

ASLR을 끄고 환경 변수가 스택으로 이동하여 공간을 차지하므로 환경 변수를 제거하십시오.

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

한도는 내 ulimit -s(8MiB) 보다 약간 낮 습니다. 다음과 같이 추가 System V 지정 데이터가 환경에 추가로 스택에 초기에 추가 되었기 때문입니다. Assembly의 Linux 64 명령 행 매개 변수 | 스택 오버플로

이것에 대해 진지한 경우 TODO 는 스택 상단에서 쓰기를 시작하고 중단 되는 최소 initrd 이미지 를 만든 다음 QEMU + GDB실행합니다 . dprintf스택 주소를 인쇄하는 루프 에을, 에 중단 점을 설정하십시오 acct_stack_growth. 영광 스러울 것입니다.

관련 :


2

기본적으로 최대 스택 크기는 프로세스 당 8MB로 구성
되지만 다음을 사용하여 변경할 수 있습니다 ulimit.

kB로 기본값 표시 :

$ ulimit -s
8192

무제한으로 설정 :

ulimit -s unlimited

현재 쉘 및 서브 쉘 및 해당 하위 프로세스에 영향을줍니다.
( ulimit쉘 내장 명령입니다)


cat /proc/$PID/maps | grep -F '[stack]'
Linux 에서 사용중인 실제 스택 주소 범위를 표시 할 수 있습니다 .


따라서 현재 쉘에 의해 프로그램이로드되면 OS는 ulimit -s프로그램에 대해 KB 의 메모리 세그먼트를 유효 하게 만듭니다 . 제 경우에는 10240KB입니다. 그러나 로컬 배열을 선언 char a[10240*1024]하고 설정 a[0]=1하면 프로그램이 올바르게 종료됩니다. 왜?
Amos

마지막 요소도 설정하십시오. 그리고 최적화되지 않았는지 확인하십시오.
vinc17

@amos vinc17이 의미하는 바는 프로그램 의 스택 맞지 않는 메모리 영역의 이름을 지정 했지만 실제로 맞지 않는 부분에서 실제로 메모리 영역에 액세스 하지 않기 때문에 머신은이를 인식하지 못합니다. 심지어 그 정보를 얻으십시오 .
Volker Siegel

@amos 시도 int n = 10240*1024; char a[n]; memset(a,'x',n);... 세그 결함.
goldilocks

2
@amos 보시다시피 a[]10MB 스택에 할당되지 않았습니다. 컴파일러는 재귀 호출이 불가능하고 특별한 할당이나 불연속 스택 또는 간접적 인 것과 같은 것을 수행했음을 알 수 있습니다.
vinc17
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.