통합검색
· 마을서비스란?  · 포럼마을  · 일반마을  · 테마마을  · 마을랭킹  · 활동왕
· 덱스퍼트란?  · TECBOX   · PRSBOX   · 이용안내  
· DEXT제품군  · 솔루션베이  · S/W & ESD 컴포넌트
· 프로그램베이
· LiveSeminar  · LiveConference
Visual C++ 포럼마을 입니다.
  마을등급 Visual C++   이 마을은 포럼마을 입니다이 마을은 자유가입제 마을 입니다 마을소개 페이지로 이동 전입신청
마을촌장촌장 나성훈 주민 34036 since 2006-12-29
우리마을 공지사항
질문&답변
강좌&팁
자유게시판
자료실
앨범
개인게시판
마을 게시판
등록된 마을 게시판이
없습니다.
랑데브 게시판
칼럼 게시판
개발자 고충상담
Dev Talk
자유토론방
벼룩시장
재나미 우스개
구인/프로젝트 정보
사람인 채용 게시판
  고객지원 게시판
마이 데브피아
 나의 e-Money 내역
 활동왕 My Page
 스크랩한 게시글보기
 쪽지관리
 주소록관리

 강좌&팁
 SAL과 컴파일 시간을 이용한 시큐어코딩   | VC++ 일반 2018-05-02 오전 8:08:42
 kyh2984@hotmail.com  kyh2984@hotmail.com님께 메시지 보내기kyh2984@hotmail.com님을 내 주소록에 추가합니다.kyh2984@hotmail.com님의 개인게시판 가기 번호: 8879 추천:0  / 읽음:1,797

개발자라면 평소보다 신나게 긴 코드를 작성한 후 컴파일 했을 때, 아무런 불평불만 없이 컴파일이 완료되면서 에러와 경고(Warning)가 없다는 메세지가 나타났을 때의 기쁨을 잘 알 것이다. 실제 제품에 반영되는 코드라면 이러한 기쁨을 뒤로한 채 디버깅이나 유닛 테스트를 이용해 검증작업을 계속 해야 할 것이다. 하지만 최근 컴파일러의 발전과 통합개발툴(IDE)의 기능이 향상돼, 코드 검증작업을 하거나 애플리케이션을 동작 시켜야만 발견할 수 있었던 다양한 문제점을 컴파일 완료 이전에 발견할 수 있게 되었다.

 

완성도 높은 코드를 만드는 방법 중 하나는 컴파일러가 생성해주는 경고 메세지의 원인을 모두 제거하는 것이다. 컴파일러에서 ‘모든 경고 사용(EnableAllWarnings, /Wall)’을 인자로 사용하기를 권장한다. 하지만 표준 라이브러리의 헤더에서 발생하는 많은 경고들이 불편하다면 최소 ‘수준 4(Level 4, /W4)’으로 설정해, 컴파일러가 평가하는 잠재적인 소스코드의 문제점을 파악하는 것이 좋다. 프로젝트 속성 화면에서 ‘경고를 오류로 처리(Treat Warnings As Errors, /WX)’로 설정하고 경고를 에러와 동일한 수준으로 간주해, 빌드 전에 반드시 문제를 해결하는 것도 권장한다.

 

<화면1> 프로젝트 속성 페이지이나 명령줄을 통해 경고 수준을 설정한다.

 

경고 메세지 끄기

기본으로 경고 수준을 높게 설정하는 것을 권장 한다. 하지만 오픈소스 코드나 외부 라이브러리 및 모듈의 문제라 해결하기 곤란하거나 오래됐지만 지속적으로 사용해온 검증된 소스코드를 재사용하는 경우라면, 경고 수준을 낮추기보다는 선택적으로 경고 메시지를 끄는 것이 좋다. 개발자가 불편해하는 대표적인 경고는 ‘C4996’일 것이다. 비주얼 스튜디오 2003 이전 버전 개발 툴로 작성된 오래된 소스코드를 사용하거나 ‘strcpy’에 대응되는 함수인 ‘strcpy_s’ 처럼 보안 함수군을 사용하지 않은, 특히 C언어 소스코드를 재사용 할 때 많이 볼 수 있다.

 

<코드1> C4996 경고 메시지를 만나게 되는 코드

void CopyString(char * dest) {

  char const * str1 = "Hello World";

  strcpy(dest, str1); // C4996

}

컴파일러 경고 메세지:

warning C4996: 'strcpy': This function or variable may be unsafe. Consider using strcpy_s instead.

 

프로젝트를 컴파일할 때 해당 경고 메시지를 검사에서 생략해 해당 기능을 끌 수 있다. 프로젝트 속성 페이지에서 프로젝트 설정 시큐어 코딩 ‘SDL(Security Development Lifecycle) 검사’ 항목을 ‘아니요(/sdl-)’로 체크한다.

 

<화면2> SDL 검사 항목을 설정해 시큐어 코딩 검사를 생략할 수 있다.

 

소스코드 상에서 경고 메시지 생략을 제어하는 가장 잘 알려진 방법 중 하나는, 해당 경고를 생략하도록 동작시키는 컴파일러 지시자인 ‘#pragma’를 이용하는 방법이다.

 

<코드 2> 문제가 있는 라인의 C4996 경고 메시지를 생략하는 코드

void CopyString(char * dest) {

  char const * str1 = "Hello World";

#pragma warning ( push )

#pragma warning ( disable : 4996 )

  strcpy(dest, str1);

#pragma warning ( pop )

}

 

마찬가지로 컴파일러 지시자를 이용하면, 특정 경고 메시지만 에러로 간주하게 설정할 수도 있다.

 

<코드3> C4996 경고 메세지만 에어로 간주하는 컴파일러 지시자

#pragma warning (error: 4996)

 

컴파일 옵션의 명령줄에 ‘/wd4996’를 전달해, 해당 경고 메시지 생략을 전역에 적용할 수도 있다. 또 다른 방법은 문제가 있는 함수군에 경고 생략 상수를 선언하는 방법이다. 보안 함수군을 사용하지 않아 나오는 경고 메시지들을 생략하고 싶다면,  ‘_CRT_SECURE_NO_WARNINGS’를 헤더파일을 선언하기 전에 ‘#define’으로 선언해주거나 컴파일러 상수로 추가해 준다. 전역 변수를 선언했을 경우에는 ‘_CRT_SECURE_NO_WARNINGS_GLOBALS’를 선언하면 된다.

 

<코드4> 체크된 이터레이터를 사용하지 않는다면 C4996 경고를 만나게 된다

#define _ITERATOR_DEBUG_LEVEL 2

#include <algorithm>

#include <iterator>

using namespace std;

using namespace stdext;

int main() {

  int a[] = { 1, 2, 3 };

  int b[] = { 10, 11, 12 };

  // copy(a, a + 3, b + 1);   // C4996

  copy(a, a + 3, checked_array_iterator<int *>(b, 3)); // OK!

}

 

C99나 C++ 최신 표준 이전의 ‘POSIX’ 함수를 사용한 경우에는 ‘_CRT_NONSTDC_NO_WARNINGS’를, 체크된 이터레이터와 관련됐다면 ‘_ITERATOR_DEBUG_LEVEL’에 ‘0’, ‘1’, ‘2’ 값을 넣고, ATL이나 MFC라면 ‘_AFX_SECURE_NO_WARNINGS’와 ‘_ATL_SECURE_NO_WARNINGS’를, 사용하지 않는 구 라이브러리 함수나 전역 변수와 관련된 경고 메시지에는 ‘_CRT_OBSOLETE_NO_WARNINGS’를 선언한다.

각 선언은’ /D_CRT_OBSOLETE_NO_WARNINGS’와 ‘/D<상수>’와 같은 형태로 컴파일러 명령줄에 추가할 수도 있다.

 

정적 코드 분석기의 사용

런타임 시에 발견하거나 발견되는 문제점 수정은 비용이 많이 든다. 컴파일 타임에 이를 발견할 수 있다면 비용을 줄이는 측면에서 가장 좋은 방법일 것이다. 경고 수준을 ‘레벨4’로 설정해서 컴파일러가 보기에 문제가 없을 것 같다고 판단했지만, 실제 코드를 분석해 보면 문제가 발생할 수 있는 코드 실수를 발견할 수 있는 가긍정적 판단(False Postive) 상황이 있다. 꼭 런타임 시에만 발견할 수 있는 오류는 아니지만, 코드 자체는 정확해서 경고가 발생하지 않는 것이다. 예를 들어 컴파일 시간에 판단한 if문의 비교문 결과가 항상 ‘0’이 되는 경우를 들 수 있다.

 

<코드5> 0을 연산자 &으로 판단한 if문

#define FLAG 0

void foo(int f) {

  if (f & FLAG) {

    Process();  // 수행되지 않는 코드(Dead code)

  }

}

 

if문의 비교문 결과가 항상 ‘0’이 된다는 것은 코드를 잘못 작성했을 가능성도 있지만 개발자가 디버깅 등의 용도로 사용한 의도적인 코드일 경우를 배재할 수 없다. 컴파일러는 개발자 의도를 정확하게 알지 못하기 때문에, 이런 일반적인 사용성을 가진 코드는 아무런 경고 메시지를 내지 않고 정상적인 코드로 간주한다.

 

정적 분석 도구를 이용하면 컴파일러가 판단하지 못했던 다양한 상황에 대한 문제를 사전에 파악할 수 있다. 시중에는 많은 상용, 오픈소스, 무료 정적 분석 도구가 있다. 컴파일 시간이 더 걸린다는 단점을 제외하면 완성도 높은 코드를 만드는데 도움을 주기 때문에 개발 시 사용할 것을 권장한다.

 

마이크로소프트(MS)도 무료로 사용할 수 있는 우수한 성능의 정적 분석 도구를 제공하고 있다. 인터넷을 검색해보면 커맨드 라인을 통해 사용할 수 있는 정적 분석 도구에 대한 강좌를 찾아볼 수 있다. 하지만 점차 통합되고 있으며 최근에는 외부 도구들이 비주얼 스튜디오 통합개발툴(IDE)에 모두 포함됐다. 프로젝트 속성 페이지의 옵션 체크 만으로 프로젝트에 쉽게 적용할 수 있게 됐다.

 

솔루션 속성 페이지에서는 솔루션 전체에 대한 코드 분석 관리나 프로젝트별 정적 분석 정책을 결정할 수 있다. 솔루션 탐색기에서 솔루션을 선택하고 렌치모양 아이콘을 클릭해 솔루션 속성 페이지를 열 수 있다.

 

<화면3> 솔루션을 선택하고 렌치 모양의 아이콘을 클릭한다.

 

솔루션 속성 페이지의 공통 속성에서 코드 분석 설정을 확인하면, 각 프로젝트별 정적 분석 규칙을 지정할 수 있다. 기본 값은 ‘Microsoft 관리 권장 규칙’이지만 ‘Microsoft 모든 규칙’ 사용을 추천한다.

 

<화면4> 솔루션 속성 페이지에서 프로젝트별 정적 코드 분석 규칙을 지정할 수 있다.

 

코드 분석 규칙을 지정했다면 메뉴의 분석(Analyze) 탭에서 ‘코드 분석 실행(Run Code Analysis)’ 아래에 솔루션 전체 혹은 현재 프로젝트에 대한 코드 분석을 실행할 수 있다.

 

<화면5> 정적 코드 분석 실행

 

빌드시마다 정적 코드 분석을 실시할 수도 있다. 프로젝트 속성의 구성 속성에서 ‘코드 분석’ 메뉴로 들어가자. 일반에서 ‘빌드에 코드 분석 사용’을 체크하고 ‘이 규칙 집합 실행’을 ‘Microsoft 모든 규칙’으로 설정한다. 열기 버튼을 누르면 정적 분석 규칙에 대한 설정파일에서 적용되는 규칙에 대한 목록과 설명을 확인할 수 있다.

 

<화면6> 프로젝트 속성 페이지에서 프로젝트 별 정적 코드 분석을 활성화 시킬 수 있다.

 

<코드6>의 코드를 작성 한 후, 메뉴의 ‘코드 분석 실행’을 선택하거나 프로젝트 속성 페이지에서 ‘빌드에 코드 분석 사용’을 체크 후 빌드하면 경고 레벨 4에서도 등장하지 않던 워닝 메시지를 확인할 수 있다.

 

<코드6> 정적 분석이 문제를 찾아낸 foo1, foo2 함수 구현

enum States {

  STATE1, STATE2, STATE3

};

void foo1() {

  const unsigned int flag = STATE1 | STATE2;

  if (flag & States::STATE1) {

    Process(); // C6313

  }

}

void foo2(int * p, int n) { // C6011

  int * q = nullptr;

  if (n > 10)

    q = p;

  *p += 2;

  if (n < 120)

    *q += 12;

}

 

문제를 확인하기 위해 메뉴의 보기(View)에서 ‘오류 목록(Error List)’을 선택해 오류를 확인한다. 에러 리스트 창의 ‘C6313’ 항목을 보면, 트리 확장을 통해 에러 메세지의 자세한 사항을 확인할 수 있다. 해당 항목을 선택하면 소스코드상의 문제가 있는 위치를 노란색 영역으로 확인할 수 있다.

 

<화면7> 오류 목록 창의 경고 메시지

 

‘C6011’ 항목의 경고 아이콘 앞부분을 보면 윈도우 아이콘이 있다. 윈도우 아이콘이 있는 경고 항목은 정적 분석기가 판단한 문제가 있는 위치와 원인에 대해 코드 분석 세부 정보 윈도우를 통해 자세히 안내해 준다.

 

<화면8> 코드 분석 세부 정보 화면

 

정적 코드 분석 과정은 개발자가 눈으로 코드를 따라가면서 코드의 결함을 찾는것과 유사하다. 다만 코드 분석기는 개발자 보다 꼼꼼하고, 개발자는 좀 더 휴리스틱하다. 하지만 코드 분석기가 마냥 꼼꼼할 수는 없다. 수많은 루프와 반복되는 함수 호출, 이에 따른 각종 값의 전달과 결과 값의 추론은 결국 미정다항 시간문제(NP문제, Nondeterministic Polynomial time problem)이기 때문이다. 정적 코드 분석기에 개발자의 의도와 소스코드의 의미를 전달해 준다면 꼼꼼하면서 휴리스틱하게 만들 수 있을 것이다.

 

SAL을 이용한 정적 코드 분석

어노테이션(Annotation)은 메타데이터를 활용해 코드 가독성을 높여주는 기술이다. ‘#define’을 통해 선언되는 상수나 어트리뷰트(Attribute) 등을 이용해 어노테이션을 추가하면, C++ 문법을 지키면서 컴파일 결과를 달라지지 않게 하면서 개발자의 의도나 코드의 의미를 나타낼 수 있다.

 

<코드7> OUT을 정의해 레퍼런스 타입으로 전달한 foo의 인자가 반환 값임을 안내하는 코드

#define OUT

void foo(OUT Obj& a);

 

마이크로소프트는 소스코드에 개발자의 의도와 소스코드의 의미를 추가할 수 있는 ‘마이크로소프트 표준 소스 코드 어노테이션 언어(Microsoft’s standard source code annotation language)’로 ‘SAL(Standard Annotation Language)’을 제공해 주고 있다. SAL은 정적 분석 도구가 인식할 수 있는데, 이를 이용하면 정적 분석 도구가 코드를 더 정확하게 분석할 수 있게 된다. 실제로 수행 중에는 문제 없을 것 같은 코드지만, 정적도구의 판단은 실패하는 가부정적 판단(False Negative) 상황 등을 막을 수 있다.

 

<코드8> SAL이 적용된 CRT 함수 wtoi의 예

_Check_return_ _CRTIMP int __cdecl _wtoi(_In_z_ const wchar_t *_Str);

 

모든 윈도우 API와 CRT(C Runtime Function) 함수 등 마이크로소프트가 제공하는 모든 헤더 파일에는 SAL이 반영돼 있다. 다만 초창기에는 CRT 함수에 SAL 어노테이션이, 윈도우 API에는 SAL의 헤더 어노테이션이 반영돼 있었다. SAL 어노테이션은 언더바 하나(_)로 시작하는 반면, 헤더 어노테이션은 언더바 두개(__)로 시작한다. CRT 함수에 적용된 어노테이션은 외부 오픈 소스 등에서 언더바 두개로 시작하는 변수 몇몇 상황들 때문에 이와 겹치지 않는 형태로 만들어 졌다. 언더바 두개로 시작하는 객체, 변수 및 함수 등은 마이크로소프트 코드 내부에서 사용하는 형식이기 때문에 임의 사용을 지양해 달라는 불문율이 여기에도 적용됐다.

 

<코드9> 헤더 어노테이션이 적용되어 안내되었던 윈도우 API

__checkReturn BOOL WINAPI

DeleteTimerQueueTimer(

  __in_opt HANDLE TimerQueue,

  __in   HANDLE Timer,

  __in_opt HANDLE CompletionEvent

  );

 

비주얼 스튜디오 2005가 출시됐을 때는 언더바 두개로 시작하는 형태의 헤더 어노테이션을, 비주얼 스튜디오 2010이 출시되었을 때는 어트리뷰트를 이용한 어노테이션을 권장했지만, 그 이후에는 SAL을 권장했다. 비주얼 스튜디오 2012부터는 언더바 하나로 시작하는 SAL 2.0으로 통합, 확장됐다.

 

<코드10> 어트리뷰트를 이용한 어노테이션의 예

#include <CodeAnalysis\SourceAnnotations.h>

void f([SA_Pre(Null=SA_No)] char** pc);

 

SAL을 사용하기 위해서는 ‘sal.h’를 소스코드에 ‘include’해 사용한다. 마이크로소프트가 배포한 윈도우 애플리케이션에 포함되는 대부분의 헤더파일은 ‘sal.h’의 내용이 포함돼 있다. 만일 SAL을 사용하고 싶지 않다면 ‘no_sal2.h’를 소스코드 맨 처음에 포함시키면 된다. SAL은 마이크로소프트만의 기술이기 때문에 크로스 플랫폼을 지원하는 소스코드의 경우, 프로젝트 성격에 따라 SAL을 사용하지 않아야 되는 경우에 유용하다.

 

현재 SAL은 함수 인자 및 반환 값 관련 어노테이션, 함수 동작 방식 관련 어노테이션, 뮤텍스 등 동기화 객체 관련 어노테이션, 동기적 비동기적 함수 관련 어노테이션, 인트린직 관련 어노테이션을 제공해 주고 있으며 계속 내용이 업데이트되고 있다.

 

함수의 포인터 인자와 관련된 대표적인 어노테이션에는 ‘_In_’, ‘_Out_’, ‘_Inout_’, ‘_Deref_out_’ 등이 있다. ‘_In_’은 포인터를 인자로 받는 함수 내부적으로 포인터가 가리키는 데이터를 사용할 때 해당 포인터 변수 인자에 사용되고, ‘_Out_’은 반환 값 변수 인자에 사용된다. ‘_Inout_’은 인자로 값을 전달해 함수 내부적으로 변경될 가능성이 있을 때 사용하고, ‘_Deref_out_’은 이중 포인터인 경우 사용한다.

 

<화면 9> 어노테이션이 사용돼 함수의 기능을 코드 분석 도구에 전달하여 경고를 생성

 

함수의 포인터 인자지만 ‘nullptr’을 허용할 경우에는 ‘opt_’를 붙여 ‘_In_opt_’, ‘_Out_opt_’, ‘_Inout_opt_’와 같이 사용하고, 문자열과 같이 널 문자로 끝나는 인자일 경우 ‘z_’를 붙여 ‘_In_z_’, ‘_Out_z_’ 등과 같이 사용하며, ‘nullptr’을 허용하면서 널 문자로 끝나는 인자일 경우 이 둘을 합해 ‘_In_opt_z’, ‘_Out_opt_z_’ 등과 같이 사용할 수 있다. ‘opt_’와 ‘z_’를 같이 기존 어노테이션에 붙여 기능을 더하는 어노테이션을 보조 어노테이션이라 한다. 자주 사용되는 보조 어노테이션으로 ‘reads_’와 ‘writes_’가 있고 여기에 ‘_bytes’ 어노테이션을 추가할 수 있다. 보조 어노테이션들은 서로 중첩할 수 있어 ‘_In_reads_opt_z_’처럼 사용할 수 있다.

 

<코드11> _In_reads_byptes_를 이용해 정적 분석 도구가 인자의 크기를 점검하게 할 수 있다.

void Foo(_In_reads_bytes_(3 * sizeof(POINT)) POINT* param1) {

  LONG sum = 0;

  for (int i = 0; i < 3; i++)

    sum += param1[i].x;

}

 

만일 인자가 레퍼런스 타입이라면 ‘_Outref_’, ‘_Outref_result_maybenull_’, ‘_Outref_result_buffer_’, ‘_Outref_result_buffer_maybenull_’와 같은 인자를 사용할 수 있다. 레퍼런스 타입에 ‘_Inref_’는 없는데, ‘_Inref_’를 위반한 것은 컴파일 에러 상황이므로 없는 것이 당연하다.

 

반환 값에 사용할 수 있는 대표적인 어노테이션에는 ‘_Ret_z_’, ‘_Ret_maybenull_’, ‘_Ret_notnull’, ‘_Ret_null_’, ‘_Ret_valid_’, ‘_Ret_writes_’, ‘_Ret_writes_maybenull_’, ‘_Ret_writes_bytes_to_maybenull_’ 등이 있다.

 

<코드12> 함수 반환 값에 크기를 점검할 수 있다.

_Ret_writes_(3) int* Foo() {

  auto retval = new int[3];

  retval[0] = 42;

  retval[1] = 42;

  retval[2] = 42;

}

 

SAL 전체를 설명하기 위해서는 많은 시간과 지면이 필요하다. 최신 버전의 비주얼 스튜디오 및 SDK에 포함된 ‘sal.h’에는 자세한 설명과 함께 대략적인 사용법이 포함돼 있다. 간단한 영어 단어의 조합 수준이므로 시간 내어 살펴 보면 정적 분석을 적극 활용하는 품질 좋은 코드를 작성할 수 있을 것이다.

 

레퍼런스

1. https://blog.naver.com/drvoss/221107488338

2. 코드 분석을 사용하여 C/C++ 코드 품질 분석

https://msdn.microsoft.com/ko-kr/library/ms182025.aspx

3. SAL 2 Function Parameters Annotations

https://www.codeproject.com/Reference/879527/WebControls/

코멘트쓰기
  좋음   놀람   궁금   화남   슬픔   최고   침묵   시무룩   부끄럼   난감
* 코멘트는 500자 이내(띄어쓰기 포함)로 적어주세요.
목록 보기   지금 보고 계시는 글을 회원님의 my Mblog >> 스크랩에 넣어두고 다음에 바로 보실 수 있습니다.  
회사소개  |   개인정보취급방침  |  제휴문의  |   광고문의  |   E-Mail 무단수집거부  |   고객지원  |   이용안내  |   세금계산서
사업자등록번호 안내: 220-81-90008 / 통신판매업신고번호 제 2017-서울구로-0055호 / 대표: 홍영준, 서민호
08390, 서울시 구로구 디지털로32길 30, 1211호 / TEL. 02_6719_6200 / FAX. 02-6499-1910
Copyright ⓒ (주) 데브피아. All rights reserved.