배너
닫기

테크노트

배너

‘스택 메모리’ 효율적으로 사용하는 노하우

  • 등록 2018.10.04 11:00:46
URL복사

[첨단 헬로티]


Stack 메모리


펌웨어, RTOS 사용 등의 MCU 제어를 위한 소프트웨어 동작 중 데이터 영역의 메모리를 쓰고 지우면서 많은 활용을 한다. 이때 중요한 메모리 영역 중 하나로 스택(Stack)영역이 있다. 일반적으로 스택은 후입 선출(LIFO - Last In, First Out) 특성을 가지며 함수의 로컬 변수, 함수 파라미터, 함수 호출을 위한 리턴값, 브랜치 주소 등 동작에 중요한 데이터들이 쓰이고 지워지는 영역이다. 즉, 소프트웨어 동작에 반드시 필요한 메모리 영역이다. 스택 메모리는 중요한 만큼 철저한 관리 또한 필요하다. 사이즈를 너무 작게 설정하면, 동작 중 스택 메모리 영역을 넘어 사용하게 되는(스택 오버플로우) 동작에 문제가 생긴다. 반대로 사이즈를 너무 크게 잡은 경우, 스택 오버플로우 현상은 없지만 다른 데이터가 쓰여질 영역이 줄어들어 메모리 관리에 비효율적이다. 


스택 영역의 정적, 동적인 방법으로 검사를 하고 모니터링을 한 후, 적합한 메모리 크기 설정 등 효과적이고 효율적으로 스택을 사용하길 바란다. 


Stack 사용량 정적 분석


작성된 소프트웨어 빌드 과정 중 링킹 과정에서 정적 분석 방법으로 스택의 최대 사용량을 계산할 수 있다. 일반적으로 컴파일 과정에서 각각 함수들의 최대 스택 사용량 정보는 컴파일러가 만들어낼 수 있다. 이 정보에 각 함수별 콜 그래프와 최대 함수 호출 깊이 등 전체적인 스택 사용의 정보가 있으면 정확한 스택 사용의 정보를 실제 코드 동작없이 정적으로 확인할 수 있다. 참고로 콜그래프 정보는 별도 “stack usage control”을 작성해 정보를 추가해야한다. 


• 정적 스택 사용량 분석 사용하기

Project Option > Linker > Advenced > Enable stack usage analysis


▲ 사진 1.


또한 정적 분석된 스택 사용량 확인을 위하여 링커 맵파일 생성이 반드시 필요하다. 기본적으로 링커 옵션 중 맵파일을 생성하도록 설정돼 있다.


Project Option > Linker > List > Generate linker map file 


▲ 사진 2.


[사진 2]와 같이 옵션 설정 후 빌드를 하면 링커 맵파일에 스택 분석항목이 추가돼 출력된다. 다음은 간단한 스택의 정적 분석 예다. 일반적으로 프로그램 엔트리와 인터럽트 핸들러는 다른 함수에 의해 호출되지 않기 때문에 콜 그래프 루트로 간주된다. 아래 예제에서 최대 스택 깊이는 프로그램 항목 호출 그래프 루트 (__iar_program_start)의 경우 288 바이트이고 인터럽트 호출 그래프 루트(__interrupt_170 과 _default_handler)의 경우 전체 120 바이트다.



• 간접 호출 지정

간접 호출은 함수 포인터를 통해 함수를 호출하는 것을 의미한다. 호출 대상의 함수는 링커에서 알 수 없기 때문에 링커는 간접 호출에 대한 스택 사용 정보를 자동으로 검색 할 수 없다. 다음의 예와 같이 경고 메시지가 링커에서 생성된다. 



링커 맵 파일 내용 중 Stack Usage 항목에는 다음과 같이 메시지가 기록된다. 



이 문제를 해결하려면 코드작성에서 #pragma calls 지시문을 사용하여 명령문에서 간접적으로 호출 할 수 있는 함수를 나열해한다. 이 지시문은 간접 호출 문 바로 앞에 삽입하고 가능한 모든 호출 수신자 함수의 목록을 지정해야 한다. 예를 들어, 다음 코드는 함수 UartRxHandler (), UartTxHandler (), UartFaultHandler ()가 함수 포인터 isr ()을 통해 간접 호출 될 수 있음을 지정한다. 



• 콜 그래프 루트 정보 제공

RTOS를 사용하는 멀티 태스킹 환경에서 각 태스크의 루트 기능은 호출 그래프 루트이기도하다. 링커가 자동으로 식별 할 수없는 경우도 있다. 링커는 다른 함수에 의해 호출되지 않았기 때문에 다음과 같은 경고 메시지를 생성한다.



링커 맵 파일 내용 중 Stack Usage 항목에는 다음과 같이 메시지가 기록된다.



이 문제를 해결하려면 다음 예와 같이 개발자가 #pragma call_graph_root 지시문을 사용해 특정 함수를 호출 그래프 루트로 식별해야 한다. 



실제로, 콜 그래프나 인터럽트 이외의 문자열을 호출 그래프의 루트 카테고리의 이름으로 사용할 수 있다. 컴파일러는 인터럽트 및 태스크 함수에 자동으로 콜 그래프 루트 카테고리를 지정한다.


이러한 정적인 스택 분석 방법은 애플리케이션을 실행하기 전 최대의 스택사용량을 확인할 수 있으므로 스택의 최적화된 사이즈를 결정하는데 많은 도움이 될 수 있다. 최적화된 스택의 사이즈 지정으로 메모리의 불필요한 낭비를 줄이고 스택의 오버플로우 위험으로부터 벗어날 수 있다.


동적 스택 사용 확인


정적 스택 사용량 분석은 이론적으로 최대의 스택 사용량을 확인할 수 있다. 그러나 실제 애플리케이션의 동작에서는 실제 스택의 사용량은 다양하게 변경될 수 있다. IAR Embedded Workbench의 C-SPY 디버거는 응용 프로그램이 실행되기 전에 스택 영역 전체에 특정패턴 (예 : 0xCD)을 채울 수 있어 편리하다. 전체의 스택 영역에서 특정 패턴이 얼마나 남아있는지를 사용해 스택 사용량을 표시한다. 일반적으로 실제 동작에서 확인되는 내용을 바탕으로 스택 사이즈를 정할 수 있지만 모든 런타임 시나리오를 정확하게 반영하지 못하는 경우를 대비해 약간의 여유 공간을 확보하는 것이 현명 할 수 있다.


[그림 3] Tools > Option 의 IDE Option 설정 창에서 Stack > Enable graphical stack display and stack usage tracking 를 선택해 C-SPY 디버거 동작 중 동적 환경에서 스택의 사용량을 그래피컬하게 확인할 수 있다. 

 

▲ 사진 3.


[그림 4] 스택 창은 C-SPY 디버거 동작 중 View 메뉴에서 활성화할 수 있다. 실행이 중지 될 때마다 C-SPY는 Stack 창에서 스택 사용에 대한 그래픽 표현을 업데이트 한다.


▲ 사진 4.

 

사용량 그래프 바에서 어두운 회색 영역은 사용 된 스택 메모리를 나타내며 밝은 회색 영역은 사용되지 않는 스택 메모리를 나타낸다. 스택 사용량이 IDE 옵션 대화 상자에서 설정할 수 있는 임계값을 초과하면 그래픽 스택 막대가 빨간색으로 바뀐다. Stack 창을 이용해 실제의 애플리케이션 동작 중 스택의 사용량과 사용의 자세한 사항을 확인하며 디버깅한다.


Stack protection (스택 보호) 


소프트웨어에서 스택 버퍼 오버플로우는 일반적으로 고정된 길이의 버퍼로 되어진 데이터 구조 외부 메모리 주소에 값을 쓸 때 발생한다. 그 결과 스택 내 의도하지 않은 데이터 값의 손상되고 심지어 함수의 리턴 주소가 손상돼 프로그램이 잘못 동작하는 경우도 있다. (참고로, 고의적으로 스택의 데이터 값을 임의로 변경하는 것을 스택 스매싱이라고 한다.) 스택 버퍼의 오버플로우를 방지하는 방법 중 하나로 석탄 광산에 유독가스 발생을 대비해 카나리아를 데리고 들어가는 것과 유사하게 스택 카나리아를 사용하는 것이다. IAR Embedded Workbench 버전 8.20부터는 이러한 스택 보호 기능을 사용할 수 있다.


• Stack protection(스택 보호) 기능 사용방법

IAR Embedded Workbench for ARM의 스택 보호 기능은 함수 상황에 따라 필요할 수도, 필요하지 않을 수도 있다. 정의 된 지역 변수에 배열이나 배열의 멤버가 포함 된 구조체가 있으면 함수에 스택 보호가 필요하다. 또한 로컬 변수의 주소가 함수 외부에서 사용되는 경우 해당 함수도 스택 보호가 요구된다.


함수가 스택 보호를 필요로 하는 경우, 배열 변수를 함수 스택 블록에서 가능한 한 높게 배치 할 수 있도록 로컬 변수가 정렬된다. 이러한 변수 다음에 카나리아 요소가 배치되고, 카나리아는 스택 보호 기능 사용 시작에 자동 초기화되며 초기화 값은 전역 변수 __stack_chk_guard에서 가져온다. 함수 종료시, 코드는 카나리아 요소가 여전히 초기화 값을 유지하고 있는지 확인한다. 만일 카나리아 값이 변경됐다면 스택에 문제가 생긴 것으로 판단해 __stack_chk_fail 함수를 호출한다.


[그림 5] 스택 보호 기능 사용을 위해 --stack_protection컴파일러 옵션을 사용한다. 


▲ 사진 5.


• Application 프로젝트에서의 작업

스택 보호기능을 사용하기 위해서는 애플리케이션 프로젝트에서 다음과 같은 작업이 필요하다.


extern uint32_t __stack_chk_guard 

전역 변수 __stack_chk_guard는 처음 사용하기 전에 초기화 시켜야 한다. 초기화 값이 무작위로 지정되면 보다 안전하다.


__interwork __nounwind __noreturn void __stack_chk_fail(void) 

__stack_chk_fail 함수의 목적은 스택에 발생한 문제에 대해 알리고 응용 프로그램을 종료하는 것이다. 이 함수의 반환 주소는 스택 문제 발생의 함수를 가리킨다.


(프로그램 설치경로)armsrclibruntime 디렉토리의 stack_protection.c 파일을 __stack_chk_guard 및 __stack_chk_fail의 템플릿으로 사용할 수 있다.


맺음말


스택 메모리는 애플리케이션 동작에 매우 중요한 정보들이 저장되는 메모리다. 스택 오버플로우, 잘못된 메모리 접근으로 스택 메모리의 잘못된 수정 등 스택 메모리 사용의 문제가 생긴다면 애플리케이션은 더 이상 정상적으로 동작 할 수 없다. 


정적, 동적인 방법 그리고 의도하지 않은 문제 검출을 위항 스택 보호 기능으로 스택을 분석, 모니터링해 최적화된 스택 사이즈 정의와 문제없는 애플리케이션을 만들기 바란다. 


글: 이현도 IAR 시스템즈(IAR Systems) 기술지원팀 과장(Hyun-Do.Lee@iar.com)









배너










주요파트너/추천기업