일반적인 라이센스

BSD

  • 소스코드를 공개하지 않아도 되는 대표적 라이센스
  • 저작권 명시
  • Nginx(BSD 2-Clause License)

MIT

  • MIT에서 해당 대학의 SW 공학도를 위해 개발한 라이센스
  • 라이센스 및 저작권 명시
  • Bootstrap, Angular.js, Backbone.js, jQuery

Apache

  • 아파치 재단의 모든 SW에 적용
  • BSD 의무사항 + 특허권
  • GPL2.0으로 배포되는 코드와 동시 적용 불가능
  • Android v2.0, Hadoop v2.0

주의해야할 라이센스

LGPL

  • 수정한 소스코드 LGPL로 공개 (Static Linking 시 전체 코드 공개)
  • 라이센스 및 저작권 명시
  • Mozilla Firefox v2.1

GPL

  • GPL 소스코드를 이용한 소프트웨어 전체 GPL로 공개
  • 라이센스 및 저작권 명시
  • Linux Kernel v2.0

AGPL

  • AGPL 소스코드를 이용한 소프트웨어 전체 AGPL로 공개
  • 라이센스 및 저작권 명시
  • MongoDB v3.0

라이센스 적용 범위

(외부 배포)

바이너리, 3rd-party, 소스코드, public, 판매, 웹서비스(AGPL 한정)

GPL License

GPL 사용시

: GPL을 사용한 프로젝트를 배포한 경우, 그 프로젝트 전체 소스코드를 공개해야 함.

GPL2.0 + Apache2.0

: GPL2.0과 Apache2.0은 특허 보복 조항에 있어 양립할 수 없기에 배포가 불가능하다.

: Apache2.0 - 특허 보복 조항 허용

: GPL2.0 - 특허 보복 조항 보장하지 않음.

LGPL2.1 + Apache2.0

: 이 또한 위와 같이 특허 보복조항에 있어 양립할 수 없기에 경우에 따라 배포가 불가능 하다.

: LGPL2.1 - 특허 보복 조항 보장하지 않음.

: LGPL2.1을 사용한 코드가 Apache2.0을 이용한 코드와 독립적으로 사용(Dynamic Linking)되는 경우 배포 가능

주요 공개SW 라이센스 의무사항

주요 라이센스 의무사항 MIT BSD Apache License 2.0 GPL 2.0 GPL 3.0 AGPL 3.0 GPL 2.1 EPL MPL
복제, 배포, 수정의 권한 허용 있음 있음 있음 있음 있음 있음 있음 있음 있음
배포 시 라이선스 사본 첨부 있음   있음 있음 있음 있음 있음 있음 있음
저작권 고지 사항 유지 있음 있음 있음 있음 있음 있음 있음 있음 있음
배포 시 소스코드 제공의무 (Reciprocity)와 범위       All All Include network Object code & Static link Module File
수정 시 수정내용 고지     있음 있음 있음 있음 있음 있음 있음
명시적 특허라이선스의 허용     있음   있음 있음   있음 있음
라이선시가 특허소송 제기 시 라이선스/특허 종료(특허 보복 조항)     Patent   있음 있음   있음 있음
이름, 상표, 상호에 대한 사용제한   있음 있음           있음
보증의 부인 있음 있음 있음 있음 있음 있음 있음 있음 있음
책임의 제한 있음 있음 있음 있음 있음 있음 있음 있음 있음

 

 

Reference

자료 참고
GPL의 재배포 이슈: 링크 32pg

Intro

개발을 하며 git branch -a를 이용해 현재 branch는 무엇인지 확인할 일이 많다.

하지만 이렇게 매번 branch name을 확인하기 위해 동일한 명령어를 반복적으로 쓰는것은 매우 번거로운 일이 아닐 수 없다.

따라서 다음의 과정을 통해 Shell 내에서 branch name을 자동으로 표시할 수 있도록 하자.

How to

아래 명령어는 Ubuntu 20.04를 기준으로 수행되었습니다.

  1. gedit ~/.bashrc

  2. 스크롤을 내려 가장 아래에

    parse_git_branch() {
    git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/(\1)/'
    }
    export PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[01;31m\]$(parse_git_branch)\[\033[00m\]\$ '

    입력

  3. terminal 재 시작

Result

그러면 아래의 사진과 같이 Linux shell에 branch name이 표시된 것을 볼 수 있다.

Reference
ask ubuntu - How do I show the git branch with colours in Bash prompt?

GTEST 설치하기

: GTEST는 google에서 개발한 C++ testing framework이다. 이를 이용해 손쉽게 TC를 만들어 개발한 코드 또는 API의 유효성을 검증할 수 있다.

다음은 이를 설치하는 방법이다.

sudo apt-get install libgtest-dev
cd /usr/src/gtest
sudo cmake CMakeLists.txt
sudo make

cd ./lib
sudo cp *.a /usr/lib

간단한 예제를 통해 살펴보기

아래와 같이 calculator API가 있다고 가정하자.

// calculator.h
class calculator
{
private:
    /* data */
public:
    int plus(int var1, int var2);
    int minus(int var1, int var2);
    int divide(int var1, int var2);
    int multiply(int var1, int var2);
};
// calculator.cpp
#include <iostream>
#include "calculator.h"

int calculator::plus(int var1, int var2)
{
    std::cout << var1 + var2 << std::endl;

    return var1 + var2;
}

int calculator::minus(int var1, int var2)
{
    std::cout << var1 - var2 << std::endl;

    return var1 - var2;
}

int calculator::divide(int var1, int var2)
{
    std::cout << var1 / var2 << std::endl;

    return var1 / var2;
}

int calculator::multiply(int var1, int var2)
{
    std::cout << var1 * var2 << std::endl;

    return var1 * var2;
}

위의 calculator.h, calculator.cpp 에서 제공하는 plus, minus, divide, multiply API의 동작(간단한 테스트를 위해 divide by zero 등과 같은 것들은 고려하지 않았습니다)을 검증하기 위해 다음과 같이 TC를 작성할 수 있다.

// main.cpp
#include "calculator.h"
#include "gtest/gtest.h"

namespace {

class CalculatorTest : public ::testing::Test
{
    public:
    calculator gCalculator;

    protected:
    CalculatorTest() {}

    virtual ~CalculatorTest() {}
    virtual void SetUp()
    {
        std::cout << "Setup" << std::endl;
    }

    virtual void TearDown()
    {
        std::cout << "TearDown" << std::endl;
    }
};

TEST_F(CalculatorTest, plus_p)
{
    EXPECT_EQ(2, gCalculator.plus(1, 1));
}

} // namespace

int main(int argc, char **argv)
{
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

이렇게 작성한 파일들을 다음의 명령으로 컴파일 및 실행하면

g++ -o main main.cpp calculator.cpp -lgtest -lpthread
./main
./main 
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from CalculatorTest
[ RUN      ] CalculatorTest.plus_p
Setup
2
TearDown
[       OK ] CalculatorTest.plus_p (0 ms)
[----------] 1 test from CalculatorTest (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[  PASSED  ] 1 test.

이렇게 TC의 동작 결과를 확인할 수 있다.

More about GTEST

위의 예제에서 EXPECT_EQ()을 통해 TC의 동작을 확인하였다. EXPECT_EQ처럼 사용할 수 있는 구문은 다음과 같다.

ASSERT_@@EXPECT_@@의 차이는 ASSERT_@@의 경우 TC에서 Fail이 발생하였을 경우 해당 위치 아래의 TC들은 더이상 실행하지 않고 종료하는 반면, EXPECT_@@의 경우 TC에서 Fail이 발생하여도 아래의 TC들을 계속해서 실행한다.

이러한 내용들을 아래에서는 ASSERT_@@Fatal assertion, EXPECT_@@Nonfatal assertion으로 부른다.

Basic Assertion

: 참과 거짓을 테스트하는 구문이다.

Fatal assertion Nonfatal assertion Verifies
ASSERT_TRUE(condition); EXPECT_TRUE(condition); condition is true
ASSERT_FALSE(condition); EXPECT_FALSE(condition); condition is false
     

Binary Comparison

: 비교문을 통해 테스트하는 방법입니다.

Fatal assertion Nonfatal assertion Verifies
ASSERT_EQ(val1, val2); EXPECT_EQ(val1, val2); val1 == val2
ASSERT_NE(val1, val2); EXPECT_NE(val1, val2); val1 != val2
ASSERT_LT(val1, val2); EXPECT_LT(val1, val2); val1 < val2
ASSERT_LE(val1, val2); EXPECT_LE(val1, val2); val1 <= val2
ASSERT_GT(val1, val2); EXPECT_GT(val1, val2); val1 > val2
ASSERT_GE(val1, val2); EXPECT_GE(val1, val2); val1 >= val2
     

String Comparison

: String의 내용에 대해서 비교하여 테스트하는 방법입니다.

Fatal assertion Nonfatal assertion Verifies
ASSERT_STREQ(str1,str2); EXPECT_STREQ(str1,str2); 두 개의 C String이 동일한 값을 가지고 있을 때
ASSERT_STRNE(str1,str2); EXPECT_STRNE(str1,str2); 두 개의 C String이 서로 다른 값을 가지고 있을 때
ASSERT_STRCASEEQ(str1,str2); EXPECT_STRCASEEQ(str1,str2); 두 개의 C String이 대/소문자를 고려하지 않고 동일한 값을 가지고 있을 때
ASSERT_STRCASENE(str1,str2); EXPECT_STRCASENE(str1,str2); 두 개의 C String이 대/소문자를 고려하지 않고 다른 값을 가지고 있을 때
     

TEST_F와 TEST의 차이

: 위에서 사용하지는 않았지만 TEST_F외에도 TEST 구문이 존재한다.

TEST_F 의 경우에는 SetUp(), TearDown()을 통해서 TC수행 이전에 필요한 변수, 값들을 설정하고 TC가 끝난 이후에 할당한 변수, 값들을 해제하는 작업을 손쉽게 행할 수 있다. 만약 Instance 생성, Network 연결 등의 작업을 TC 수행 전에 반복적으로 해야하는 경우에는 SetUp()내에 이러한 작업을 해 두면 손쉽게 TC를 진행할 수 있다.

간단하게 말해서

  1. SetUp() 수행
  2. TEST_F(Fixture, add) 수행
  3. TearDown() 수행
  4. SetUp() 수행
  5. TEST_F(Fixture, diff) 수행
  6. TearDown() 수행

위의 순서대로 수행된다고 보면 된다.

TEST의 경우에는 위의 SetUP(), TearDown()이 필요없는 간단한 TC의 경우에 사용하면 좋을 듯 하다.

설치 오류

: 설치 후 gtest를 사용하기위해 #include "gtest/gtest.h" 를 하였을 때, 아래와 같은 오류를 본 적이 있는가?

Googletest/gtets: fatal error: gtest.h: No such file or directory

그렇다면 이 페이지(jayy-h.tistory.com/21)에서 알려주고 있는 설치 가이드를 따르기 바란다. 나 또한 위와 같은 오류를 겪었고, 오랜 시간 발품을 팔아 확인한 솔루션이다.

 

Reference 
googletest 공식문서

make란?

: 이전 게시물에서 gcc를 통한 컴파일 명령어들을 살펴 보았다. gcc의 문제는 프로젝트의 크기가 커질 때, gcc를 통한 컴파일이 상당히 번거로운 작업이 될 것이다. 따라서 이를 위해 파일간의 종속 관계를 기술해 컴파일 명령을 순차적으로 수행하는 것이 make이다.

간단한 Makefile

다음과 같은 파일 구조가 있다고 가정하자.

jay@jay-VirtualBox:~/tutorial/make$ tree .
.
├── plus.c
├── calculator.h
├── main.c
└── minus.c

이를 위한 간단한 Makefile은 다음과 같을 것이다.

all: calculator

calculator : minus.o plus.o main.o
    gcc -W -Wall -o calculator minus.o plus.o main.o

minus.o : minus.c
    gcc -W -Wall -c -o minus.o minus.c

plus.o : plus.c
    gcc -W -Wall -c -o plus.o plus.c

main.o : main.c
    gcc -W -Wall -c -o main.o main.c

clean : 
    rm -rf *.o diary

여기서 make 명령어를 사용하면, Makefile 내에서 제일 처음 오는 타겟 을 찾는다.
all 타겟의 종속 항목인 minus.o가 만들어지지 않았으므로 minus.o를 생성하는 룰을 찾아 해당 명령을 수행하며, 이는 반복적으로 수행되어진다.

매크로를 사용한 Makefile

: 매크로를 정의하면 이는 Makefile 내에 정의된 문자열로 치환되어 사용되어 진다. 이는 일관성있고 이식성 높은 Makefile을 만드는 데 도움을 준다. 다음은 매크로 작성의 규칙이다.

  1. #은 주석문을 의미한다.
  2. 정의한 매크로를 참조할 때에는 다음의 방법이 있다.
    • NAME = string
    • ${NAME}
    • $(NAME)
    • 여기서 중괄호는 셸의 환경변수 치환에도 사용되므로, 소괄호를 사용하는 것을 권장한다.
  3. 정의되지 않은 매크로는 null 문자열(공백)로 치환된다.
  4. 중복 정의된 경우 최후에 정의된 값으로 치환된다.
  5. 매크로 정의 시, 이전에 정의한 값을 참조해 정의할 수 있다.
    • NAME2 = my valuable $(NAME)
  6. 대입에는 다음의 방법들이 있다.
    • NAME1 = string: 재귀적 확장 매크로, 대입문에 post-defined variable이 사용될 경우 이를 계속적으로 스캔하며 대입한다.
    • NAME2 := string: 단순 확장 매크로, 대입문에 pre-defined variable만이 대입된다.
    • NAME3 += string: 기존의 매크로에 공백을 두고 덧 붙인다.
    • NAME4 ?= string: NAME4가 기존에 정의되어있지 않은 경우 이 명령을 통해 정의하고, 그렇지 않다면 무시한다.

NOTE

대입문에 따옴표(")를 넣으면 따옴표(") 또한 문자열로 인식하여 대입한다.

 

내부적으로 정의되어 있는 매크로

: 내부적으로 정의되어 있는 매크로를 확인하기 위해서는 make -p 명령을 사용하면 된다. 다음은 그 일부를 나타낸다.

# default
CC = cc
# environment
_ = /usr/bin/make
# default
CHECKOUT,v = +$(if $(wildcard $@),,$(CO) $(COFLAGS) $< $@)
# environment
MANAGERPID = 1220
# environment
CLUTTER_IM_MODULE = ibus
# environment
LESSOPEN = | /usr/bin/lesspipe %s
# environment
LC_NAME = ko_KR.UTF-8
# default
CPP = $(CC) -E
# default
LINK.cc = $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)
# default
MAKE_HOST := x86_64-pc-linux-gnu
# environment
PATH = /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
# default
LD = ld

...
...

# Implicit Rules

%: %.o
#  recipe to execute (built-in):
    $(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@

%.c:

%: %.c
#  recipe to execute (built-in):
    $(LINK.c) $^ $(LOADLIBES) $(LDLIBS) -o $@

%.ln: %.c
#  recipe to execute (built-in):
    $(LINT.c) -C$* $<

%.o: %.c
#  recipe to execute (built-in):
    $(COMPILE.c) $(OUTPUT_OPTION) $<

%.cc:

%: %.cc
#  recipe to execute (built-in):
    $(LINK.cc) $^ $(LOADLIBES) $(LDLIBS) -o $@

%.o: %.cc
#  recipe to execute (built-in):
    $(COMPILE.cc) $(OUTPUT_OPTION) $<

여기서 주요하게 살펴볼 것들은 CC, CXX, CFLAGS, CXXFLAGS, LD 등이 있다.

또한 다음의 자동 매크로 리스트를 살펴보자.

매크로 설명
$? 현재 타겟보다 최근에 변경된 종속 항목 리스트
(확장자 규칙에서 사용 불가)
$< 현재 타겟보다 최근에 변경된 종속 항목 리스트
(확장자 규칙에서만 사용 가능)
$* 현재 타겟보다 최근에 변경된 종속 항목의 이름(확장자 제외)
(확장자 규칙에서만 사용 가능)
$^ 현재 타겟의 종속 항목 리스트
(확장자 규칙에서 사용 불가)
$@ 현재 타겟의 이름
$% 현재 타겟이 라이브러리 모듈일 때, .o 파일에 대응되는 이름

위의 자동 매크로를 이용하면 아래와 같이 편리하게 기술할 수 있다.

target : dep1.c dep2.c
    gcc -o target dep1.c dep2.c
CC = gcc
CFLAGS = -W -Wall
TARGET = calculator

all : $(TARGET)

$(TARGET) : dep1.c dep2.c
    $(CC) $(CFLAGS) -o $@ $^

확장자 규칙의 사용

: *.c 파일을 오브젝트로 컴파일(gcc의 -c 옵션)하면 그에 상응하면 *.o파일이 생성된다. 이를 이용한 것이 make -p 명령어의 아래에서 볼 수 있는

%.o: %.c
#  recipe to execute (built-in):
    $(COMPILE.c) $(OUTPUT_OPTION) $<

등의 pre-defined 명령문이다. 따라서 gcc는 확장자 규칙에 맞게 생성된 파일들(*.cc, *.java, *.c)의 파일을 보고 암묵적으로 적절한 컴파일러를 이용해 컴파일한다. 이를 이용하면 위의 Makefile은 더욱 간단해 질 수 있다.

OBJECTS = minus.o plus.o main.o

all : calculator
calculator : $(OBJECTS)
    $(CC) -o $@ $^
clean :
    rm -rf *.o calculator

위의 기술문에서 calculator를 생성하기 위한 종속항목들(minus.o, plus.o, main.o)을 생성하기 위해 확장자가 .o인 내부 확장자 규칙을 이용해 현재 디렉토리에서 minus.o, plus.o, main.o를 생성할 파일을 찾는다. 이러한 파일들은 확장자를 제외하고 동일한 이름을 가지고 있어야 한다.

더 나아가서 추가적으로 내부 확장자 규칙을 재 정의할 수 있다. 내부 확장자 규칙에서 디버깅 메시지를 활성화하고 싶은 경우, 다음과 같이 재정의 할 수 있다.

OBJECTS = minus.o plus.o main.o

.SUFFIXES : .o .c
%.o : %.c          # .o와 대응되는 .c를 발견 시 아래 명령어를 수행한다.
    $(CC) -DDEBUG -c -o $@ $<

all : calculator
calculator : $(OBJECTS)
    $(CC) -o $@ $^
clean :
    rm -rf *.o calculator

여기서 확장자 규칙을 재 정의하기위해 .SUFFIXES라는 특수 타겟을 사용했다. .SUFFIXES종속 항목은 확장자 규칙을 검사하는 데 사용되는 확장자들의 리스트이다. 따라서 확장자 규칙을 사용하려는 확장자는 먼저 .SUFFIXES 타겟의 종속 항목으로 설정되어야 한다.

여기서 %는 일치하는 확장자를 제외한 파일명을 의미한다.

프로젝트의 파일이 수 없이 많을 경우 이를 모두 기술하는 것은 거의 불가능에 가깝다. 따라서 다음과 같이 편리하게 만들 수 있다.

SRCS = $(shell ls *.c)
SRCS = $(wildcard *.c)
OBJECTS = $(SRCS:.c=.o)
  • $(shell 실행할 셸 명령): 셸 명령을 수행하고 결과를 리턴한다.
  • $(wildcard *.c): *.c와 일치하는 파일들을 공백으로 구분하여 대입
  • $(SRCS:.c=.o): 대입 참조기법을 사용해 .c가 .o로 바뀐다.

NOTE

clean:
    cd ~/Desktop/tutorial
    rm -rf *.o
    @echo "with @ option print only result, not with command itself"

위와 같은 명령을 수행하면 원하는 결과를 얻지 못할 수 있다.
각각의 명령은 새로운 셸을 띄워

/bin/sh -c cd ~/Desktop/tutoril
/bin/sh -c rm -rf *.o

를 수행하므로 이에 유의해야한다.

.

NOTE

echo $?
위 명령을 수행하면 직전에 수행한 명령의 리턴값을 나타낸다. 0일 경우 ERROR_NONE을 나타낸다.

재귀적 make의 사용

all : calculator.o plus.o minus.o main.o
    $(CC) -o calculator $^

calculator.o:
    cd calculator && make

...

: 재귀적으로 Makefile을 선언해 사용할 경우, 부모 Makefile에 기술한 매크로를 자식 Makefile에 전달하고 싶을 때가 있다. 이 경우 다음과 같이

TARGET := calculator
export CC = gcc

export 구문을 사용한다. 만약 모든 매크로에 대해 전달하고 싶을 경우 export 구문을 단독으로 사용하면 된다.

valgrind를 이용한 메모리 디버깅

: valgrind는 할당되지 않은 포인터 변수에 값을 넣는 것, malloc과 free를 수행하며 dangling pointer가 발생되거나 이중 free가 발생되는 문제와 같이 memory leak, invalid memory reference 등의 메모리 관련 문제점을 찾아주는 도구이다.

: 다음은 이러한 valgrind를 어떻게 사용하는지를 나타내는 예제이다.

#include <stdlib.h>
#include <iostream>

using namespace std;

int main()
{
    int *pInt = NULL;
    int cnt = 100;
    pInt = (int *)malloc(sizeof(int) * cnt);

    for (int i = 0; i < cnt; i++) {
        pInt[i] = i;
    }

    cout << pInt[cnt] << endl;

    for (int i = 0; i < cnt; i++) {
        free(&pInt[i]);
    }

    return 0;
}
Pre-requisite

valgrind를 사용하기 위해서는 해당 툴이 설치되어있어야 한다.
설치는 다음의 명령어를 통해 할 수 있다.

$ sudo apt  install valgrind

이제 다음의 명령어를 통해 컴파일한다.

jay@jay-VirtualBox:~/tutorial/valgrind$ g++ -o memory -g memory.cpp
jay@jay-VirtualBox:~/tutorial/valgrind$ ls
memory  memory.cpp

그 후, memory leak과 관련된 오류가 발생하였는지 다음의 명령어를 통해 알 수 있다. 이는 valgrind가 해당 프로그램을 수행시켜보며 메모리 관련된 문제가 발생하였는지를 판단하는 것이므로 test-case를 통해 memory leak을 판단하고자 할 때, test-case가 작성한 코드의 모든 범위와 조건들을 충족하는지에 따라 검출되는 버그가 다르다는 것에 유의하길 바란다.

jay@jay-VirtualBox:~/tutorial/valgrind$ valgrind --tool=memcheck --leak-check=yes --leak-resolution=high -v ./memory 
==39434== Memcheck, a memory error detector
==39434== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==39434== Using Valgrind-3.15.0-608cb11914-20190413 and LibVEX; rerun with -h for copyright info
==39434== Command: ./memory
==39434== 
--39434-- Valgrind options:
--39434--    --tool=memcheck
--39434--    --leak-check=yes
--39434--    --leak-resolution=high
--39434--    -v

...

==39604== 99 errors in context 2 of 2:
==39604== Invalid free() / delete / delete[] / realloc()
==39604==    at 0x483CA3F: free (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==39604==    by 0x1092AA: main (memory.cpp:19)
==39604==  Address 0x4dafc84 is 4 bytes inside a block of size 400 free'd
==39604==    at 0x483CA3F: free (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==39604==    by 0x1092AA: main (memory.cpp:19)
==39604==  Block was alloc'd at
==39604==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==39604==    by 0x109214: main (memory.cpp:10)

lcov, genhtml을 이용한 coverage 깔끔하게 보기

NOTE
이전의 gocv를 이용한 커버리지 정보 확인과 밀접한 관계가 있는 글 입니다.
이 글을 풍부하게 이해하기 위해서는 gcov 관련 게시글을 보고 오시길 바랍니다.
Pre-requsite

gcov의 결과를 웹 페이지를 통해 깔끔하게 보여주기 위해 lcov를 사용한다.
이를 위해 다음의 명령어를 이용해 lcov 설치를 진행하자.

sudo apt install lcov

우선 다음과 같은 프로그램이 있다고 가정하자.

// main.cpp

#include <iostream>
using namespace std;

void func(int dividend, int divisor)
{
    cout << "Incoming variable: " << dividend << " " << divisor << endl;

    int quotient = 0;
    int remainder = 0;
    try {
        if (divisor == 0)
            throw exception();
        quotient = dividend / divisor;
        remainder = dividend % divisor;
    } catch (...) {
        cerr << "Exception occured!" << endl;
        quotient = 0;
        remainder = dividend;
    }

    switch(remainder) {
    case 0:
        cout << "Dividend is multiplier of divisior" << endl;
        break;
    case 100:
        cout << "Cannot reach here under this circumstances!" << endl;
        break;
    default:
        cout << "Nothing" << endl; 
        break;
    }
}

int main()
{
    for (int i = 0; i < 100; ++i) {
        for (int j = 0; j < 100; ++j) {
            func(i, j);
        }
    }

    return 0;
}

다음의 명령어를 이용해 컴파일을 수행한다.

g++ -o main main.cpp -fprofile-arcs -ftest-coverage -g

여기서 -fprofile-arcs -ftest-coverage 옵션은 소스의 각 베이직 블록에 프로파일링 코드를 삽입하라는 옵션이다.

이 명령을 수행하면 다음과 같은 파일들이 생성된다.

jay@jay-VirtualBox:~/tutorial/gcov$ g++ -o main main.cpp -fprofile-arcs -ftest-coverage -g
jay@jay-VirtualBox:~/tutorial/gcov$ ls
main  main.cpp  main.gcno

./main을 통해 프로그램을 한 번 수행시킨다. 이는 다음의 main.gcda 파일을 생성시킨다.

jay@jay-VirtualBox:~/tutorial/gcov$ ./main
jay@jay-VirtualBox:~/tutorial/gcov$ ls
main  main.cpp  main.gcda  main.gcno

gcov main.cpp를 수행한다.

jay@jay-VirtualBox:~/tutorial/gcov$ gcov main.cpp 
File 'main.cpp'
Lines executed:89.29% of 28
Creating 'main.cpp.gcov'

File '/usr/include/c++/9/iostream'
No executable lines
Removing 'iostream.gcov'

File '/usr/include/c++/9/bits/exception.h'
Lines executed:100.00% of 1
Creating 'exception.h.gcov'

jay@jay-VirtualBox:~/tutorial/gcov$ ls
exception.h.gcov  main  main.cpp  main.cpp.gcov  main.gcda  main.gcno

lcov --rc lcov_branch_coverage=1 --capture --directory ${gcov_data_file_directory} --output-file {output_file_name} 명령을 통해 gcov 데이터 파일을 이용해 genhtml에 쓰일 파일을 생성한다.

jay@jay-VirtualBox:~/tutorial/gcov$ lcov --rc lcov_branch_coverage=1 --capture --directory ./ --output-file generated.info
Capturing coverage data from ./
Found gcov version: 9.3.0
Using intermediate gcov format
Scanning ./ for .gcda files ...
Found 1 data files in ./
Processing main.gcda
Finished .info-file creation
jay@jay-VirtualBox:~/tutorial/gcov$ ls
exception.h.gcov  main      main.cpp.gcov  main.gcno
generated.info    main.cpp  main.gcda

여기서 --rc lcov_branch_coverage=1 옵션은 분기 커버리지를 보기 위한 옵션으로 genhtml에서도 쓰인다.

마지막으로 genhtml ${info_file} --branch-coverage --output-directory ${output_directory} 명령으로 coverage 정보를 담는 html 파일을 만든다.

jay@jay-VirtualBox:~/tutorial/gcov$ genhtml generated.info --branch-coverage --output-directory ./
Reading data file generated.info
Found 2 entries.
Found common filename prefix "/usr/include/c++/9"
Writing .css and .png files.
Generating output.
Processing file /home/jay/tutorial/gcov/main.cpp
Processing file bits/exception.h
Writing directory view page.
Overall coverage rate:
  lines......: 89.7% (26 of 29 lines)
  functions..: 100.0% (3 of 3 functions)
  branches...: 76.9% (10 of 13 branches)
jay@jay-VirtualBox:~/tutorial/gcov$ ls
amber.png         generated.info     index-sort-f.html  main.gcda
bits              glass.png          index-sort-l.html  main.gcno
emerald.png       home               main               ruby.png
exception.h.gcov  index.html         main.cpp           snow.png
gcov.css          index-sort-b.html  main.cpp.gcov      updown.png

이렇게 되면 다음과 같이 coverage 정보를 시각적으로 아름답게 표현해주는 html 파일이 생성되었다.

 

genhtml을 통해 생성된 파일

gcov를 통한 커버리지 정보 알아보기

: gcov는 gcc에 포함된 유틸리티로 코드 커버리지를 파악하는 데 쓰인다. 이는 어떤 부분이 얼마만큼 수행되는지, 어느 부분이 한 번도 수행되지 않는지를 파악할 수 있고, 이런 정보를 이용해 불필요한 코드 제거에 사용되어질 수 도 있다. 앞서 설명한 gprof와 비교하자면, gprof는 함수 단위의 프로파일링 정보만을 제공하는 반면에 gcov는 베이직 블록 단위의 프로파일링을 수행해 함수 내 어떤 루프가 많이 수행되었는지, if-else 블록에서 어느 블록이 많이 수행되었는지를 알 수 있다.

// main.c
#include <stdio.h>

void func1()
{
    printf("Hello World \n");
}

void func2(int delay)
{
    printf("Delay: %d", delay);

    while (delay--);
}

int main()
{
    for (int i = 0; i < 10; ++i) {
        func1();
    }

    for (int i = 0; i < 100; ++i) {
        func2(i);
    }

    return 0;
}

위와 같은 파일이 있다고 할 때, gcov를 이용한 라인 커버리지를 확인하는 방법은 다음과 같다.

  1. gcc -o main main.c -fprofile-arcs -ftest-coverage -g 명령으로 컴파일을 수행한다.
    • -fprofile-arcs -ftest-coverage 옵션은 소스의 각 베이직 블록에 프로파일링 코드를 삽입하라는 옵션이다.
  2. ./main을 통해 프로그램을 한 번 수행시킨다.
    • 이는 main.gcda, main.gcno 파일을 생성시킨다. 이 두 파일에 소스파일의 베이직 블록에 대한 프로파일링 정보가 포함된다.
  3. gcov main.c 명령을 수행하여 main.c.gcov파일을 생성시킨다.
  4. cat main.c.gocv 명령을 통해 프로파일링 정보를 확인한다.
  1: 1: #include <stdio.h>
  -: 2: 
  -: 3: void func1()
 10: 4: {
 10: 5:     printf("Hello World \n");
 10: 6: }
  -: 7: 
  -: 8: void func2(int delay)
100: 8: {
...
  • 각 라인의 맨 앞에 있는 숫자는 해당 라인이 몇 번 호출되었는지를 나타낸다.

readelf 명령으로 ELF파일의 각종 정보 보기

: readelf 명령은 EFL 파일의 각종 정보를 보기 위한 명령이다. 사용 가능한 옵션은 아래와 같다.

옵션 설명
-a 모든 정보를 출력
-h ELF 헤더 정보를 출력
-l 프로그램 헤더를 출력
-S 섹션 헤더를 출력
-e 모든 헤더를 출력
-s 심볼 테이블을 출력
-n note 섹션의 정보를 출력
-r 재배치 섹션의 정보를 출력
-d 동적 섹션의 정보를 출력

: 더 많은 정보를 원한다면, readelf --help 명령을 통해 알아보자.

'빌드&테스트도구 > 기타' 카테고리의 다른 글

nm - 심볼 테이블 확인  (0) 2020.09.22
gprof - 프로파일 정보 확인  (0) 2020.09.21
Mangling - 심볼명 확인  (0) 2020.09.20

+ Recent posts