개발 과정에서 지속적으로 스택 분석을 모니터링하는 방법

수천에서 수백만 줄에 달하는 코드로 구성된 임베디드 소프트웨어는 점점 더 정교해지고 있지만, 견고하고 정확하며 빠르게 동작하는 소프트웨어를 구현한다는 전반적 목표는 변하지 않았습니다. 빠르게 동작하는 소프트웨어는 가용 CPU 및 메모리 리소스를 최적의 방식으로 관리해야 하며, 이는 메모리 용량, 특히 RAM 용량이 제한된 임베디드 시스템에서 까다로운 과제입니다. 스택 분석 및 힙 분석을 수행하여 RAM 사용량을 분석하는 것이 중요합니다. 개발자가 스택 및 힙 부하를 직접 추정하는 일은 프로그램의 크기가 작은 경우에도 어려운 작업입니다. 부정확한 추정은 스택 오버플로와 정의되지 않은 동작으로 이어질 수 있습니다. 일부 코딩 표준에서 불필요한 오버헤드를 피하기 위해 메모리 할당 사용에 대한 모범 사례를 강제하는 것도 바로 이 때문입니다. 하지만 스택은 여전히 필수적인 RAM의 구성요소이며 최적의 방식으로 사용해야 합니다.

임베디드 시스템에 스택 분석이 필요한 이유

스택 오버플로는 가용 스택의 크기가 코드에서 요구되는 크기보다 작을 때 발생합니다. 그렇다고 해서 필요한 크기 이상의 스택으로 환경이 구성되면 메모리가 낭비됩니다. 개발자는 안전 필수 애플리케이션에서 최악의 경우의 스택 사용량을 지속적이고 일관적으로 추정하여 소프트웨어 실행 시 RAM이 부족해지는 상황을 방지해야만 합니다.

부정확한 스택 추정의 위험.

부정확한 스택 추정의 위험.

스택 분석 추정 방법

직접 스택 추정

직접 스택 분석을 추정하는 것이 간혹 유용할 수 있지만, 이 방법이 더 복잡한 시스템에서는 까다로울 수 있습니다. 함수 호출의 깊이와 모든 지역 변수에 대한 세부 사항 그리고 실행 중 어떤 순간에든 발생하는 인터럽트 프레임의 크기 등을 철저히 이해해야 하기 때문입니다. 이 절차는 시간이 오래 걸리고 실수가 일어나기 쉽습니다. 정적 코드 분석 툴로 빠르게 계산할 수 있다는 점을 고려해 보면 직접 하는 방법은 불필요한 작업입니다.

정적 코드 분석기 사용

개발자는 정적 코드 분석기로 스택 사용량을 예측할 수 있습니다. 분석 툴은 함수 호출 깊이, 지역 변수와 반환 파라미터에 대한 스택 추정치, 중첩 인터럽트, 실행 중에 발생하는 인터럽트의 크기를 분석할 수 있습니다. 정적 코드 분석기를 사용하면 스택 분석 추정 외에도 코딩 규칙 위반, 런타임 결함, 코딩 복잡도까지 확인할 수 있다는 장점이 있습니다. 이 작업은 단 몇 분 안에 완료되므로 개발자는 스택 사용량을 직접 계산하는 데 걸리는 시간을 절약할 수 있습니다.

타겟에서 테스트 및 계측

정적 분석기는 개발 도중에 스택 사용량을 추정할 수 있습니다. 그러나 가장 좋은 방법은 실제 하드웨어에서 실제 스택 사용량에 대한 결과를 확보하는 것입니다. 많은 개발 환경에서는 하드웨어 에뮬레이션 기능과 함께 실시간 스택 분석 수행 기능을 제공합니다. 실제 하드웨어에서 스택 분석을 수행하고 오버플로 시나리오를 생성하여 페일 세이프(fail-safe) 루틴을 테스트하는 것이 중요합니다. 이제 중요한 질문은 언제 정적 분석 툴로 스택 분석을 수행하고 언제 실제 타겟에 대해 그 분석을 수행해야 하느냐는 것입니다.

스택 분석의 수행 시점

스택 분석 수행은 소프트웨어 개발 라이프사이클에서 계속 진행되는 과정입니다. 스택 사용량을 별개의 품질 평가 팀이 소프트웨어 개발 라이프사이클의 종료 시점에만 추정한다면 전체 개발 작업에서 위험 요소가 해소되지 않을 것입니다. 또한 개발 주기의 후반부에 문제를 해결하면 오류가 발생하기 쉽고 많은 시간이 소요될 수 있으며, 하드웨어의 설계 변경을 처리할지 소프트웨어의 설계 변경을 처리할지 정하는 과정에서 혼선을 빚을 수도 있습니다. 스택 분석을 수행해야 하는 가장 중요한 시점은 다음과 같습니다.

  • 새로운 기능이 추가될 때

    소프트웨어에 새로운 기능이 추가될 때마다 스택 사용량이 증가합니다. 개발자는 새로운 기능의 스택 사용량을 추적해야 합니다.
    1. 스택 분석 수행, 디버그 및 복잡한 코드 수정: 매 주요 기능 구현 후에 개발자는 특정 소프트웨어 컴포넌트 또는 소프트웨어 모듈에 대해 정적 분석기를 로컬에서 사용하여 기준 소프트웨어와 구현된 소프트웨어를 비교해서 스택 사용량 증가폭을 평가할 수 있습니다.
    2. 개발 공정 전반에 걸친 스택 분석 모니터링: QA 팀과 제품 책임자는 정적 분석기로 CI(지속적 통합) 파이프라인에 대해 스택 추정을 수행하여 대시보드에 결과를 표시할 수 있습니다. 이 과정을 통해 소프트웨어 개발 라이프사이클 동안에 스택 분석을 추적할 수 있습니다.
    3. 최소 스택 사용량 유지를 위한 모범 사례 적용: 품질 게이트를 통해 동적 메모리 할당의 조건부 사용을 규정하는 MISRA™ 및 AUTOSAR 코딩 지침의 위반 문제를 방지할 수 있습니다.
  • 소프트웨어 출시 전

    정적 분석기 스택 추정치는 스택 사용량이 통제되고 있음을 보여주는 강력한 증거를 제공합니다. 매 소프트웨어 출시마다 그 전에 실제 타겟에 대해 표준 동작 부하, 최소 부하 및 최대 부하 조건에서 스택 분석을 실행하면 스택 사용량을 포괄적으로 이해할 수 있습니다. 스택 오버플로 및 언더플로 이벤트에 대한 페일 세이프 절차를 검증하는 것도 매우 중요합니다.

Polyspace의 스택 추정 기능

Polyspace Code Prover™는 각 함수에서 지역 변수가 크고 작을 경우에 대한 보수적 추정과 낙관적 추정을 수행하여 함수 수준과 프로그램 수준에서 최대 및 최소 스택 사용량을 도출할 수 있습니다. 이 분석에서는 함수 반환 값의 크기, 함수 파라미터의 크기, 지역 변수의 크기, 메모리 정렬에 대한 추가적 패딩을 고려합니다.

Polyspace 데스크탑에서의 스택 분석 코드 메트릭.

Polyspace 데스크탑에서의 스택 분석 코드 메트릭.

오버슈팅 스택 활용률을 이해하고 디버그하기 위해 개발자는 Polyspace®를 로컬에서 실행하고 함수 호출 깊이를 살펴봄으로써 스택 오버슈트의 정확한 원인을 파악하고 가용 리소스를 최적의 방식으로 활용하여 스택 활용량을 줄일 수 있습니다.

table_loop() 함수에 대한 호출 트리 및 상위 스택 추정.

table_loop() 함수에 대한 호출 트리 및 상위 스택 추정.

개발 공정 전반에 걸친 스택 분석 모니터링

Polyspace Access™는 웹 브라우저에서 그래픽 사용자 인터페이스를 렌더링하는 결과 데이터베이스 서버입니다. CI 공정은 Polyspace Server™에서 스택 분석을 트리거하여 스택 사용량 추정치를 생성할 수 있습니다. 이 결과는 결과 데이터베이스에 업로드할 수 있습니다. QA 팀과 제품 책임자는 스택 사용량을 그래픽 프론트엔드에서 계속 확인하고 가용 스택 리소스가 과도하게 쓰이는 경우 필요한 조처를 취할 수 있습니다.

Polyspace Access에서의 프로젝트 수준 스택 추정.

Polyspace Access에서의 프로젝트 수준 스택 추정.

이어지는 단계에서는 스택을 많이 사용하는 함수를 검토하고 개발자에게 특정 함수를 할당하여 추가로 조사하고 디버그할 수 있도록 합니다. Polyspace를 사용하면 Jira와 같은 버그 추적 툴에서 분석 결과를 개발자에게 할당하기 전에 그 결과의 상태, 심각도, 주석을 지정할 수 있습니다. 

Polyspace Access의 함수 수준 스택 추정 및 결과 검토 대시보드.

Polyspace Access의 함수 수준 스택 추정 및 결과 검토 대시보드.

최소 스택 사용량 유지를 위한 모범 사례 적용

프로덕션 코드의 경우엔 MISRA C™, MISRA C++, AUTOSAR C++ 등의 코딩 표준을 절대 위반하지 말아야 합니다. 이러한 코딩 표준에서는 동적 메모리 할당을 금지하고 정적 메모리 할당을 최적화하는 특정 활용 사례를 권장합니다. Polyspace Bug Finder™는 각종 모범 사례 위반을 식별할 수 있으며 개발자는 로컬에서, 제품 책임자는 Polyspace Access를 통해 이러한 위반 사항을 모니터링할 수 있습니다. 아래 코딩 규칙은 Polyspace Bug Finder로 분석할 수 있는 정적 메모리 할당에 대한 모범 사례를 명시하고 있습니다.

코딩 지침

규칙

설명

MISRA C: 2004

20.4

동적 힙 메모리 할당을 사용하지 않아야 합니다.

MISRA C: 2012

21.3

<stdlib.h>의 메모리 할당 및 할당 해제 함수를 사용하지 않아야 합니다.

MISRA C++: 2008

18-4-1

동적 힙 메모리 할당을 사용하지 않아야 합니다.

AUTOSAR C++14

A18-5-1

malloc, calloc, realloc 및 free 함수를 사용하지 않아야 합니다.

AUTOSAR C++14

A18-5-2

Non-placement new 또는 delete 표현식을 사용하지 않아야 합니다.

AUTOSAR C++14

A18-5-3

delete 표현식의 형식은 메모리 할당에 사용되는 new 표현식의 형식과 일치해야 합니다.

AUTOSAR C++14

A18-5-4

프로젝트에서 “delete” 연산자의 크기 지정 버전이나 비지정 버전이 전역적으로 정의된 경우에는 두 버전을 모두 정의해야 합니다.

AUTOSAR C++14

A18-5-5

메모리 관리 함수는 다음과 같은 조건을 보장해야 합니다. (a) 최악의 경우의 실행 시간의 존재로 인해 나타나는 결정적 동작, (b) 메모리 조각화 방지, (c) 메모리 부족 방지, (d) 불일치 할당 또는 할당 해제 방지, (e) 커널에 대한 비결정적 호출에 대한 비종속성.

AUTOSAR C++14

A18-5-7

프로젝트에서 동적 메모리 관리 함수의 비실시간 구현을 사용한다면 비실시간 프로그램 단계에서만 메모리를 할당 및 할당 해제해야 합니다.

AUTOSAR C++14

A18-5-8

함수보다 더 길게 지속되지 않는 객체에는 자동 저장 기간이 있어야 합니다.

AUTOSAR C++14

A18-5-9

동적 메모리 할당 및 할당 해제 함수의 사용자 지정 구현은 C++ 표준의 해당 “필수 동작” 절에 명시된 의미론적 요구사항을 충족해야 합니다.

AUTOSAR C++14

A18-5-10

Placement new는 충분한 저장소 용량에 대해 포인터가 적절히 정렬된 경우에만 사용해야 합니다.

AUTOSAR C++14

A18-5-11

“new 연산자”와 “delete 연산자”는 함께 정의되어야 합니다.

스택 사용량은 코드의 순환 복잡도, 중첩 함수 호출의 개수, 함수 내 변수의 개수 등이 증가함에 따라 늘어납니다. Polyspace는 스택 사용량에 영향을 미치는 많은 변수에 대한 제어 기능을 제공하며, 코드 복잡도의 임계값을 설정할 수 있습니다.

코드 복잡도의 임계값 설정.

코드 복잡도의 임계값 설정.

Polyspace Bug Finder는 정적 및 동적 메모리 할당에 대한 많은 런타임 검사를 제공합니다. 우선순위가 높은 결함, 중간 수준인 결함, 낮은 결함을 모두 해결하면 메모리 할당에 따른 위험을 줄일 수 있습니다.

런타임 정적 및 동적 메모리 검사.

런타임 정적 및 동적 메모리 검사.

스택의 크기는 스택 사용량 계산에 쓰이는 방법과 관계없이 약간 초과 설정하는 것이 좋습니다. 이 접근법을 통해 테스트 중에 감지되지 않았을 수도 있는 스택 오버플로로 인한 시스템 취약성을 방지할 수 있습니다.

현장에서 정의할 수 없는 동작을 보이는 많은 임베디드 애플리케이션에 대한 원인의 상당 부분이 스택 오버플로 취약성에 있습니다. 적절한 시점에 올바른 툴을 사용하고 모범 사례를 준수하면 스택 오버플로에 대한 소프트웨어의 신뢰성을 개선할 수 있습니다.