Philip Withnall
philip.withnall@collabora.co.uk 2015
다양한 작업을 처리할 올바른 도구 활용
조성호
shcho@gnome.org 2016-2018.

# 도구 활용

개발 도구는 텍스트 편집기 및 컴파일러 말고도 굉장히 많습니다. 도구를 올바르게 활용하면 디버깅, 메모리 할당 및 시스템 콜, 기타 복잡한 문제의 추적이 쉬워집니다. 대부분 일반적으로 활용하는 일부 도구를 아래에 설명했습니다만, 특수 목적으로 활용하는 도구가 있으며, 필요하다면 활용해야합니다.

대단히 중요한 원칙은 출시 시기가 닥치기 전까지 최대한 많은 디버깅 옵션을 비활성화 하기보단 개발할 때 항상 활성화하는 조치입니다. 가능한 모든 디버그 도구를 활용하여 꾸준히 코드를 테스트하면, 버그가 코드에 깊이 뿌리 박혀 없애기 어려워 지기 전 빨리 잡을 수 있습니다.

실제로, 모든 컴파일러 및 기타 도구의 경고를 활성화하고 경고 메시지가 나타났을 경우 빌드 과정의 오류로 간주하여 실패하도록 설정하라는 의미입니다.

#

        요약

          2차 컴파일러로 자주 컴파일하십시오().
          다양한 컴파일러 경고 범위를 활성화하고, 경고 항목을 치명적인 항목으로 간주하십시오().
          GDB를 활용하여 코드를 디버깅하고 한 단계씩 실행하십시오().
          Valgrind를 활용하여 메모리 사용 상태, 메모리 오류, 캐시 및 CPU 성능, 스레드 오류를 분석하십시오().
          gcov 및 lcov를 활용하여 단위 테스트 범위를 분석하십시오().
          컴파일러 정리기를 활용하여 메모리, 스레드 및 비지정 동작 문제를 분석하십시오().
          cronjob을 활용하여 Coverity에 제출하고 정적 분석 오류가 나타나는대로 수정하십시오().
          Clang 정적 분석기 및 Tartan을 주기적으로 활용하여 로컬에서 꾸준히 분석할 수 있는 오류를 하나하나 수정하십시오().

# GCC와 Clang

GCC는 리눅스용 표준 C 컴파일러입니다. Clang이라는 기능 호환 대안도 있습니다. 이 중 하나를 주 컴파일러(아마도 GCC)를 선택하겠지만, 코드에서 나타나는 오류 및 경고 출력이 약간 다르기 때문에, 경우에 따라 코드를 컴파일할 때 다른 컴파일러를 산택합니다. Clang에는 코드를 컴파일하거나 실행하지 않고도 코드의 오류를 찾아낼 수 있는 정적 분석 도구가 있습니다. 를 참고하십시오.

두 컴파일러는 가능한 많은 경고 플래그를 설정하여 사용해야 합니다. 컴파일러 경고가 때로는 거짓 양성 결과를 출력할 수도 있지만, 대부분의 경고는 코드의 문제를 정확하게 짚어내며, 이 문제를 무시하기 보단 수정해아합니다. 모든 경고 플래그를 활성화하고 `-Werror` 플래그(모든 경고를 컴파일 실패로 간주)를 지정하는 개발 정책은 나타나는대로 최대한 경고를 수정하겠다는 의지를 고취하며, 코드 품질 개선에 도움을줍니다. 경고를 무시하면, 경고로 나타난 문제로 인해 발생한 버그를 디버깅 세션 내내 오랫동안 찾게 만듭니다. 개발 기간이 끝날 때까지 경고를 무시하면, 경고를 활성화하고 경고문을 따라 문제를 수정하는데 엄청난 시간을 소비합니다.

GCC와 Clang은 다양한 범위의 컴파일러 플래그를 지원하는데, 일부는 최근 범용 코드에먼 관련이 있습니다(예를 들면, 다른 코드는 오래되었거나 특정 아키텍처에서만 동작합니다). 타당한 활성화 플래그 셋을 결정하는건 까다로울 수 있어 `AX_COMPILER_FLAGS` 매크로가 있습니다.

`AX_COMPILER_FLAGS` enables a consistent set of compiler warnings, and also tests that the compiler supports each flag before enabling it. This accounts for differences in the set of flags supported by GCC and Clang. To use it, add `AX_COMPILER_FLAGS` to configure.ac. If you are using in-tree copies of autoconf-archive macros, copy ax_compiler_flags.m4 to the m4/ directory of your project. Note that it depends on the following autoconf-archive macros which are GPL-licensed so potentially cannot be copied in-tree. They may have to remain in autoconf-archive, with that as a build time dependency of the project:

`ax_append_compile_flags.m4`

`ax_append_flag.m4`

`ax_check_compile_flag.m4`

`ax_require_defined.m4`

`AX_COMPILER_FLAGS`에서는 출시판 빌드 목적으로 `-Werror` 플래그 비활성화를 지원하므로, 출시판은 항상 더 많은 경고 메시지를 내보낼 수 있는 새 컴파일러로 빌드할 수도 있습니다. 출시판 빌드(그리고 오직 출시 빌드 용도로만) 목적으로 이 기능을 활성화하려면 세번째 매개변수 값을 ‘yes’로 설정하십시오. 개발 및 CI 빌드 과정에서는 항상 `-Werror` 플래그를 활성화해야합니다.

출시판 빌드는 `AX_COMPILER_FLAGS`에 결과를 바로 전달할 수 있는 `AX_IS_RELEASE`를 활용하여 찾을 수 있습니다:

AX_IS_RELEASE([git]) AX_COMPILER_FLAGS([WARN_CFLAGS],[WARN_LDFLAGS],[$ax_is_release])

출시 안정 정책의 선택(`AX_IS_RELEASE`의 첫번째 인자)은 프로젝트의 버전 부여 안정성을 취할 프로젝트 별로 설정해야 합니다.

# GDB

GDB는 리눅스에서 사용하는 C언어용 표준 디버거입니다. 프로그램의 갑작스러운 중단을 디버깅할 때 매우 보편적으로 사용하며, 프로그램의 코드를 실행할 때, 단계별로 살펴보려는 용도로도 활용합니다. GDB 사용 완벽 지침서는 여기에 있습니다.

소스 트리에서 프로그램 실행시 GDB를 실행하려면 libtool exec gdb --args ./program-name --some --arguments --here 명령을 사용하십시오

libtool은 몇가지 libtool 변수를 설정하는 쉘 스크립트로 소스 트리상에서 컴파일한 각각의 이진 파일에 둘러싸여있기 때문에 GDB가 필요합니다. 설치한 실행 파일을 디버깅할 때는 필요하지 않습니다.

GDB는 코드의 각 부분의 중단점에서 실행할 작은 핵심 디버깅 스크립트를 만들 때 근본적으로 합칠 수 있는 여러가지 고급 기능이 있습니다. 때로는 (참조 카운트 디버깅 같은 경우) 쓸 만한 접근 수단일 수 있지만, 어떤 경우에는 `g_debug()` 함수로 디버깅 메시지를 출력하는게 더 간단한 방법일 수도 있습니다.

# Valgrind

Valgrind는 프로그램의 동작을 측정하고 프로파일링하는 도구의 모음입니다. 제일 인기있는 도구는 memcheck지만, 몇가지 강력하고 쓸 만한 다른 도구도 있습니다. 다음 부분에서 개별적으로 다루겠습니다.

Valgrind를 활용하는 쓸 만한 방법은 Valgrind에서 확인한 수많은 오류를 나타내는 상태 코드를 반환하도록 설정한 Valgrind에서 프로그램의 단위 테스트 모음을 실행하는 방법입니다. make check의 일부로 실행할 때, Valgrind에서 문제를 찾지 못했다면 성공했다고 표시하고 그렇지 않으면 실패했다고 표시합니다. 그러나, 명령행의 Valgrind에서 make check 명령을 실행하면 항상 그렇지는 않습니다. make check-valgrind 타겟을 추가하여 명령행에서 자동으로 Valgrind를 실행하도록 `AX_VALGRIND_CHECK` 매크로를 활용할 수 있습니다. 언급한 대로 활용하려면:

ax_valgrind_check.m4 파일을 프로젝트의 m4/ 디렉터리에 복사하십시오.

configure.ac에 `AX_VALGRIND_CHECK`를 추가하십시오.

단위 테스트를 넣은 각 디렉터리의 Makefile.am 파일에 `@VALGRIND_CHECK_RULES@` 코드를 추가하십시오.

make check-valgrind 명령을 실행하면, 도구마다 하나씩 만드는 test-suite-*.log 로그 파일에 결과를 저장합니다. 단위 테스트가 있는 디렉터리마다 이 명령을 실행해야 함을 참고하십시오.

Valgrind에는 파일 숨김 기법(suppression files)으로 거짓 양성 결과를 없애는 방법이 있습니다. 이 목록으로 오류 추적 스택과 비교할 패턴을 나타냅니다. 오류로부터 나온 추적 스택이 숨김 항목의 일부와 일치하면, 보고하지 않습니다. 여러가지 이유로 GLib에서는 memcheck와 Valgrind 사용시 기본적으로 숨기는게 좋은 helgrind 및 drd에서 수많은 거짓 양성 결과를 출력합니다. 이 때문에, 프로젝트에서 지정한대로 표준 GLib 숨김 파일 목록을 사용해야합니다.

`AX_VALGRIND_CHECK` 매크로에서 파일을 숨기는 기능도 지원합니다:

@VALGRIND_CHECK_RULES@ VALGRIND_SUPPRESSIONS_FILES = my-project.supp glib.supp EXTRA_DIST = $(VALGRIND_SUPPRESSIONS_FILES)

## memcheck

memcheck는 메모리 사용 및 할당 분석기입니다. 메모리 접근 및 힙 수정 문제(할당 및 해제)를 찾아냅니다. 상당히 견고하고 잘 다듬어진 도구이며, 출력 결과는 전적으로 신뢰할 수 있습니다. ‘분명하게’ 메모리 누수가 있다고 한다면, 고쳐야 할 메모리 누수가 분명하게 있으며, ‘잠재적’ 메모리 누수가 있다면, 메모리 누수를 수정해야 하거나, 초기화 과정에서 메모리를 할당한 후 힙 메모리 공간을 해제하지 않고 프로그램 동작 내내 사용할 지도 모릅니다.

설치한 프로그램에 대해 memcheck를 실행하려면 다음 명령을 활용하십시오:

valgrind --tool=memcheck --leak-check=full my-program-name

또는 원본 디렉터리에서 프로그램을 실행한다면, 다음 명령을 사용하여 libtool 보조 스크립트에서 메모리 누수 검사 실행을 막으십시오:

libtool exec valgrind --tool=memcheck --leak-check=full ./my-program-name

(디버깅 심볼을 둔채로 컴파일 한 경우)Valgrind는 메모리 오류를 정확히 찾아 수정할 수 있도록 짧은 역추적 기록에서 자체적으로 찾아낸 메모리 문제를 나타냅니다.

memcheck 사용 완벽 지침서는 여기에 있습니다.

## cachegrind 및 KCacheGrind

cachegrind는 인스트럭션 실행도 측정할 수 있는 캐시 성능 프로파일러기 때문에 프로그램의 일반 성능 프로파일링에 매우 쓸 만합니다. KCacheGrind는 프로파일링 데이터를 시각화하고 탐색할 수 있게 하는 쓸 만한 도구이며, 언급한 두가지 도구를 따로 사용하는 경우는 드뭅니다.

cachegrind는 프로세서 메모리 계층을 시뮬레이팅하기에 완전히 정확하지 않은 상황이 있을 수도 있습니다. 그러나, 이 프로그램의 결과는 항상 주요 지점의 성능 디버깅에 충분히 대표적으로 쓸 만합니다.

cachegrind 사용 완벽 지침서는 여기에 있습니다.

## helgrind 및 drd

helgrind와 drd는 메모리 접근시 레이스 컨디션과 POSIX pthreads API의 남용을 검사하는 스레드 오류 감지기입니다. 비슷한 도구지만 다른 기법으로 구현했기에 두가지 도구를 모두 사용해야합니다.

helgrind와 drd에서 찾는 오류는 일관성있는 잠금 조치 없이 다중 스레드의 데이터 접근, 잠금 승인 순서의 바뀜, 뮤텍스를 잠그는 동안 해제 동작, 잠긴 뮤텍스의 잠금, 잠금 해제한 뮤텍스의 잠금 해제, 및 몇가지 오류입니다. 각 오류를 발견하면, 몇가지 보고서로 콘솔에 출력하며, 이 보고서에는 관련된 뮤텍스 및 스레드의 할당 또는 생성 세부 정보가 들어있어, 관련 스레드와 뮤텍스의 자세한 내용을 찾을 수 있습니다.

helgrind와 drd는 memcheck또는 cachegrind보다 거짓양성 결과를 더 많이 출력할 수 있어, 좀 더 신중하게 출력 결과를 살펴봐야 합니다. 그러나, 스레드 처리 문제는 경험이 있는 프로그래머에게 마저도 악명이 자자할 정도로 찾기 어렵기에 helgrind와 drd 오류를 가볍게 무시해선 안됩니다.

helgrind와 drd 사용 완벽 지침서는 이 곳과 이 곳에 있습니다.

## sgcheck

sgcheck는 배열의 길이를 넘겨 짚는 접근을 찾아내는 배열 범위 검사기입니다. 나온 지 얼마 되지 않은 도구이며, 여전히 시험판으로 간주하므로 다른 도구에 비해 (잘못된) 거짓 양성 결과가 나올 수 있습니다.

시험판이므로 Valgrind에 --tool=sgcheck가 아닌, --tool=exp-sgcheck 매개 변수를 전달하여 실행해야합니다.

완전한 sgcheck 사용 지침은 여기에 있습니다.

# gcov 및 lcov

gcov는 GCC로 빌드한 프로파일링 도구이며, 컴파일 시간의 추가 코드를 넣어 코드를 분석합니다. 이 프로그램을 실행할 때, 코드에서 .gcda 와 .gcno 프로파일링 출력 파일이 나옵니다. 이 파일은 코드 실행 영역의 실행 시간 시각 보고서, 다른 곳에서보단 프로젝트에서 더 많이 실행하는 코드의 줄을 강조하는 lcov 도구로 분석할 수 있습니다.

코드 범위 데이터 수집의 결정적인 사용은 단위 테스트를 수행할 때입니다. 단위 테스트로 상당히 많은 범위의 코드(어떤 줄을 실행했는지?)를 안다면 단위 테스트 범위 확장을 이끄는데 사용할 수 있습니다. 보통 단위 테스트로 얻어낸 코드 범위를 검사하고, 검사 범위가 100%에 가깝게 도달하면 전체 프로젝트를 테스트했다고 봐도 됩니다. 종종, 단위 테스트는 대분의 코드를 시험해보지만, 일부 제어문을 테스트하지 않으므로 일부 버그가 남아있을 수 있습니다.

lcov supports branch coverage measurement, so is not suitable for demonstrating coverage of safety critical code. It is perfectly suitable for non-safety critical code.

컴파일 시간 및 실행 시간에 코드 범위를 활성화하면, 제공한 메크로로 단순하게 처리할 수 있습니다. `AX_CODE_COVERAGE` 매크로는 빌드 시스템에 make check-code-coverage 타겟을 추가하여 코드 범위 활성화 후 단위 테스트를 진행하며 lcov 명령으로 보고서를 만듭니다.

프로젝트에 `AX_CODE_COVERAGE` 지원을 추가하려면:

ax_code_coverage.m4 파일을 프로젝트의 m4/ 디렉터리에 복사하십시오.

configure.ac 파일에 `AX_CODE_COVERAGE`를 추가하십시오.

최상위 Makefile.am 파일에 `@CODE_COVERAGE_RULES`를 추가하십시오.

예를 들어 모든 라이브러리를 대상으로 하지만 코드 단위 테스트를 수행하지 않는다면, 원하는 각 범위를 대상으로 automake의 `*_CFLAGS` 변수에 `$(CODE_COVERAGE_CFLAGS)`를 추가하십시오. `$(CODE_COVERAGE_LDFLAGS)` 와 `*_LDFLAGS`도 똑같이 처리하십시오.

gcov 및 lcov 문서는 여기에 있습니다.

# 주소, 스레드 및 비정의 동작 정리 프로그램

GCC와 Clang에서는 여러가지 정리기가 있습니다. 실행 시간에 추가 코드를 설정하여 프로그램에 추가로 컴파일할 부분을 검사하며, 여러가지 잘못된 동작의 플래그를 설정합니다. 강력하지만, 프로그램을 다시 컴파일할 때 이런 기능을 활성화/비활성화하려면 특히 활성화해야합니다. 여러가지를 동시에 활성화하거나 Valgrind와 같은 도구를 동시에 활용할 수 없습니다. 아직 나온 지 얼마 안됐기 때문에 다른 도구와의 집약도가 낮습니다.

GCC와 Clang에서는 동일한 여러 컴파일러 옵션을 수용하는 모든 정리 프로그램이 있습니다.

## 주소 정리기

address sanitizer(‘asan’)은 C와 C++ 프로그램에서 메모리 할당 해제 후 사용 및 버퍼 오버플로우 버그를 찾습니다. 완전한 asan 사용 지침서는 Clang용으로 있습니다만, GCC 용은 아직 작업중입니다.

## 스레드 정리기

thread sanitizer(‘tsan’)은 메모리 할당시 급하게 데이터를 넣는 코드와, POSIX 스레드 처리 API의 광범위한 오용 사례를 찾습니다. 완전한 tsan 사용 지침서는 Clang용으로 있습니다만, GCC 용은 아직 작업중입니다.

## 비정의 동작 정리기

undefined behavior sanitizer (‘ubsan’)은 C 프로그램에서 잠재적으로 정의하지 않은 여러가지 동작을 찾아내는 작은 진단 프로그램 모음입니다. ubsan 활성화 방법 모음은 Clang용으로 있습니다만, GCC 용은 아직 작업중입니다.

# Coverity

Coverity는 현존하는 상업용 정적 분석 도구 중 가장 유명하고 규모가 큰 도구 중 하나입니다. 그러나 오픈 소스 프로젝트에서 무료로 쓸 수 있으며, 어떤 프로젝트의 경우 가입하는게 좋습니다. 자체적으로 일부 분석 도구를 실행하여 분석을 진행하며, Coverity 사이트에 소스 코드를 업로드한 후 타르볼 결과를 가져옵니다. 프로젝트 참여 구성원은 결과를 프로젝트 소스코드의 주석 형식(lcov에서 결과를 보여주는 방식과 비슷)으로 온라인으로 볼 수 있습니다.

Coverity는 로컬에서만 실행할 수는 없으며 빌드 시스템에 제대로 붙일 수 없습니다. 허나, 정기적으로 프로젝트를 자동으로 검사하고 타르볼로 압축하여 Coverity로 업로드하는 스크립트는 있습니다. 추천 방식은 프로젝트 git 저장소를 온전히 체크아웃하는 스크립트를 서버에서 주기적(보통 cronjob 방식)으로 실행하는 방식입니다. Coverity는 새 정적 분석 문제를 프로젝트 구성원에게 전자메일로 알려주어 컴파일러 경고 정보를 얻을 수 있습니다: 모든 정적 분석 경고를 하나하나 없앤 후, 새로 나타나는 경고를 하나하나 처리해나가십시오.

Coverity는 우수하지만 완벽하진 않으며 거짓 양성 결과가 많이 나옵니다. 이 결과는 온라인 인터페이스에서 무시해야합니다.

# Clang 정적 분석기

자체적으로 정적 분석을 수행할 때 사용할 수 있는 도구는 Clang 컴파일러와 개발한 Clang 정적 분석기입니다. 컴파일러가 찾을 수 없어 실행 시간에만 (단위 테스트로) 찾아낼 수 있는 C 코드의 다양한 문제를 찾아냅니다.

Clang은 일부 거짓 양성 결과를 내며, 이 결과를 무시할 간편한 방법이 없습니다. 거짓 양성 출력을 수정할 수 있게 정적 분석기 버그를 보고하는 방안을 추천합니다.

Clang 사용법 완벽 안내서는 여기에 있습니다.

## Tartan

그러나, Clang 정적 분석기의 모든 역량을 위해서라면, GLib 같은 일부 라이브러리의 문제를 찾을 수 없습니다. 프로젝트가 GLib만을 사용하거나 (Clang이 이해하는) POSIX API를 적게 활용한다면 문제가 됩니다. Clang 정적 분석기에 맞춰서 일반 GLib API 일부 사용 검사를 지원하도록 Clang 기능을 확장하는데 쓸 수 있는 Tartan 플러그인이 있습니다.

Tartan은 나타난 지 얼마 안된 프로그램이며, 거짓 양성 결과를 내기도 하고, 일부 코드에서 실행할 때 갑작스럽게 끝나는 경우가 있습니다. 그러나 적절한 버그를 좀 빨리 찾을 수 있으며, 코드에서 GLib를 사용할 때 새 오류를 찾아내려는 목적으로 코드 기반을 자주 실행할 때 쓸 만합니다. Tartan 사용 중 나타나는 문제를 알려주십시오.

Clang 정적 분석기와 Tartan을 사용하는 완벽 지침서는 여기에 있습니다. 올바르게 설치했다면, 일밭 정적 분석기 출력과 Tartan 출력이 섞여서 나옵니다.