소프트웨어 개발 과정에서 함수 라이브러리를 구축할 때, 리눅스의 ar 명령어는 필수적인 도구입니다. 본 튜토리얼에서는 샘플 코드를 기반으로 정적 라이브러리를 만들고, 수정하며, 실제 프로그램에서 사용하는 전반적인 과정을 상세히 안내합니다.
ar 명령어는 1971년부터 사용되어 온 오랜 역사를 가진 명령어입니다. ‘ar’라는 이름은 원래 아카이브 파일을 생성하는 데 사용되었던 도구의 목적을 반영합니다. 아카이브 파일 생성을 위해 사용되었으며, 아카이브 파일은 여러 파일을 하나로 묶는 컨테이너 역할을 합니다. 아카이브 내에서 파일을 추가, 제거, 추출하는 기능이 가능합니다. 하지만 이러한 기능은 tar와 같은 다른 유틸리티로 대체되었으며, 현재 ar 명령어는 정적 라이브러리 생성과 같은 특정 분야에서 주로 활용됩니다. 또한 데비안 리눅스 배포판과 우분투 등에서 사용되는 “.deb” 파일과 같은 패키지 파일을 만드는 데에도 사용됩니다.
본 가이드에서는 정적 라이브러리를 만들고 수정하는 단계와, 프로그램에서 라이브러리를 어떻게 활용하는지 보여줍니다. 예시로 사용할 라이브러리는 텍스트 문자열을 암호화하고, 암호화된 텍스트를 복호화하는 기능을 제공합니다.
이 예제는 단순한 데모용으로, 실제 보안을 요구하는 환경에서는 사용하지 않아야 합니다. 우리가 사용할 암호화 방식은 아주 기본적인 대체 암호 방식으로, A는 B로, B는 C로 바뀌는 방식입니다.
cipher_encode() 및 cipher_decode() 함수 구현
“library” 디렉토리에서 작업을 진행하며, “test”라는 하위 디렉토리를 생성할 것입니다.
이 디렉토리에는 두 개의 파일이 위치합니다. cipher_encode.c라는 텍스트 파일에는 cipher_encode() 함수가 구현되어 있습니다.
void cipher_encode(char *text) { for (int i=0; text[i] != 0x0; i++) { text[i]++; } } // end of cipher_encode
cipher_decode() 함수는 cipher_decode.c라는 텍스트 파일에 있습니다.
void cipher_decode(char *text) { for (int i=0; text[i] != 0x0; i++) { text[i]--; } } // end of cipher_decode
프로그래밍 명령어가 담긴 파일을 소스 코드 파일이라고 부릅니다. 우리는 libcipher.a라는 라이브러리 파일을 만들 것입니다. 이 파일에는 두 개의 소스 코드 파일이 컴파일된 버전으로 포함됩니다. 또한, libcipher.h라는 짧은 텍스트 파일도 생성합니다. 이 파일은 새 라이브러리에 있는 두 함수의 정의를 포함하는 헤더 파일입니다.
라이브러리와 헤더 파일을 가지고 있는 사람이라면 누구나 자신의 프로그램에서 이 두 가지 기능을 사용할 수 있습니다. 이미 만들어진 기능을 재개발할 필요 없이, 단순히 우리의 라이브러리를 사용하는 것으로 충분합니다.
cipher_encode.c 및 cipher_decode.c 파일 컴파일
소스 코드 파일을 컴파일하기 위해 표준 GNU 컴파일러인 gcc를 사용합니다. -c 옵션은 gcc에게 파일을 컴파일한 후 링커 단계를 생략하도록 지시합니다. 이 과정을 통해 각 소스 코드 파일에서 오브젝트 파일이라는 중간 결과물이 생성됩니다. gcc 링커는 일반적으로 모든 오브젝트 파일을 합쳐서 실행 가능한 프로그램을 만들지만, -c 옵션을 사용하면 이 단계를 건너뛰고 오브젝트 파일만 생성하게 됩니다.
우선, 작업 디렉토리에 파일이 있는지 확인합니다.
ls -l
디렉토리 내에 두 개의 소스 코드 파일이 있습니다. 이제 gcc를 사용하여 오브젝트 파일로 컴파일합니다.
gcc -c cipher_encode.c
gcc -c cipher_decode.c
성공적으로 컴파일되면 gcc는 아무런 출력도 표시하지 않습니다.
컴파일 결과로 소스 코드 파일과 이름은 같지만 확장자가 “.o”인 두 개의 오브젝트 파일이 생성됩니다. 이러한 오브젝트 파일이 바로 라이브러리 파일에 추가될 파일들입니다.
ls -l
libcipher.a 라이브러리 생성
실질적인 아카이브 파일, 즉 라이브러리 파일을 만들기 위해 ar 명령어를 사용할 것입니다.
-c 옵션은 라이브러리 파일을 생성하는 역할을 하며, -r 옵션은 파일을 라이브러리 파일에 추가하거나 기존 파일을 대체하는 데 사용됩니다. -s 옵션은 라이브러리 파일 내부에 있는 파일들의 인덱스를 생성합니다.
우리가 생성할 라이브러리 파일의 이름은 libcipher.a 입니다. 이 파일에 추가할 오브젝트 파일들의 이름을 명령줄에 함께 입력합니다.
ar -crs libcipher.a cipher_encode.o cipher_decode.o
디렉토리 내 파일을 확인하면 libcipher.a 파일이 생성된 것을 확인할 수 있습니다.
ls -l
ar 명령어와 함께 -t 옵션을 사용하면 라이브러리 파일 내부의 모듈들을 확인할 수 있습니다.
ar -t libcipher.a
libcipher.h 헤더 파일 생성
libcipher.h 파일은 libcipher.a 라이브러리를 사용하는 모든 프로그램에 포함되어야 합니다. 이 파일에는 라이브러리에 있는 함수들의 정의가 포함됩니다.
헤더 파일을 생성하려면 gedit와 같은 텍스트 편집기를 사용하여 함수 정의를 입력해야 합니다. 파일 이름은 “libcipher.h”로 지정하고, libcipher.a 파일과 같은 디렉토리에 저장합니다.
void cipher_encode(char *text); void cipher_decode(char *text);
libcipher 라이브러리 사용
새로운 라이브러리를 제대로 테스트하기 위한 가장 확실한 방법은 간단한 테스트 프로그램을 작성하여 사용하는 것입니다. 먼저 “test”라는 디렉토리를 만듭니다.
mkdir test
라이브러리와 헤더 파일을 이 새 디렉토리로 복사합니다.
cp libcipher.* ./test
새 디렉토리로 이동합니다.
cd test
두 파일이 디렉토리 내에 있는지 확인합니다.
ls -l
라이브러리를 사용하는 작은 프로그램을 작성하여 정상적으로 작동하는지 확인해야 합니다. 다음 코드를 편집기에 입력하고, “test.c”라는 파일로 저장합니다.
#include#include #include "libcipher.h" int main(int argc, char *argv[]) { char text[]="How-To Geek loves Linux"; puts(text); cipher_encode(text); puts(text); cipher_decode(text); puts(text); exit (0); } // end of main
프로그램의 흐름은 다음과 같습니다:
- libcipher.h 파일을 포함하여 라이브러리 함수 정의를 사용합니다.
- “text”라는 문자열을 만들고 “How-To Geek loves Linux”라는 문장을 저장합니다.
- 문자열을 화면에 출력합니다.
- cipher_encode() 함수를 호출하여 문자열을 암호화하고, 암호화된 문자열을 화면에 출력합니다.
- cipher_decode() 함수를 호출하여 문자열을 복호화하고, 복호화된 문자열을 화면에 출력합니다.
테스트 프로그램을 컴파일하고 라이브러리에 링크하려면 gcc 명령어를 사용해야 합니다. -o 옵션은 gcc가 생성한 실행 파일의 이름을 지정합니다.
gcc test.c libcipher.a -o test
gcc가 아무런 오류 없이 명령 프롬프트로 돌아오면 성공적으로 컴파일된 것입니다. 이제 프로그램을 실행하여 테스트해 보겠습니다.
./test
예상대로 출력이 나타납니다. 테스트 프로그램은 일반 텍스트를 출력하고, 암호화된 텍스트를 출력한 다음, 복호화된 텍스트를 출력합니다. 이는 우리가 생성한 라이브러리가 정상적으로 작동한다는 것을 보여줍니다.
성공적으로 라이브러리를 생성하고 테스트했습니다. 하지만 여기에서 멈출 필요는 없습니다.
라이브러리에 다른 모듈 추가
이제 라이브러리에 다른 기능을 추가해 보겠습니다. 라이브러리 버전을 표시하는 함수를 추가하여 프로그래머들이 사용하는 라이브러리 버전을 쉽게 확인할 수 있도록 할 것입니다. 새로운 함수를 만들고 컴파일한 다음, 새 오브젝트 파일을 기존 라이브러리 파일에 추가해야 합니다.
편집기에 다음 코드를 입력하고, 라이브러리 디렉토리 내에 cipher_version.c라는 파일로 저장합니다.
#includevoid cipher_version(void) { puts("How-To Geek :: VERY INSECURE Cipher Library"); puts("Version 0.0.1 Alphan"); } // end of cipher_version
libcipher.h 헤더 파일에 새로운 함수의 정의를 추가해야 합니다. 파일의 마지막 줄에 다음과 같은 새 줄을 추가합니다.
void cipher_encode(char *text); void cipher_decode(char *text); void cipher_version(void);
수정된 libcipher.h 파일을 저장합니다.
cipher_version.o 오브젝트 파일을 생성하기 위해 cipher_version.c 파일을 컴파일합니다.
gcc -c cipher_version.c
컴파일 결과로 cipher_version.o 파일이 생성됩니다. 다음 명령어를 사용하여 새 오브젝트 파일을 libcipher.a 라이브러리에 추가합니다. -v 옵션은 ar 명령어가 수행하는 작업을 자세히 표시해 줍니다.
ar -rsv libcipher.a cipher_version.o
새 오브젝트 파일이 라이브러리 파일에 추가됩니다. ar은 추가 작업을 확인하는 메시지를 출력합니다. “a”는 “added”의 약자입니다.
-t 옵션을 사용하여 라이브러리 파일 내부에 어떤 모듈들이 있는지 확인할 수 있습니다.
ar -t libcipher.a
이제 라이브러리 파일 내에 세 개의 모듈이 있습니다. 새로운 기능을 사용해 보겠습니다.
cipher_version() 함수 사용
테스트 디렉토리에서 이전 라이브러리 및 헤더 파일을 제거하고, 새로운 파일을 복사한 후 테스트 디렉토리로 다시 이동합니다.
이전 버전의 파일을 삭제합니다.
rm ./test/libcipher.*
새로운 버전을 테스트 디렉토리에 복사합니다.
cp libcipher.* ./test
테스트 디렉토리로 이동합니다.
cd test
이제 새로운 라이브러리 기능을 사용하도록 test.c 프로그램을 수정할 수 있습니다.
cipher_version() 함수를 호출하는 코드를 test.c 프로그램에 추가해야 합니다. puts(text); 코드 앞 줄에 추가하겠습니다.
#include#include #include "libcipher.h" int main(int argc, char *argv[]) { char text[]="How-To Geek loves Linux"; // new line added here cipher_version(); puts(text); cipher_encode(text); puts(text); cipher_decode(text); puts(text); exit (0); } // end of main
이것을 test.c로 저장합니다. 이제 컴파일하고 새 기능이 제대로 작동하는지 테스트할 수 있습니다.
gcc test.c libcipher.a -o test
새 버전의 테스트 프로그램을 실행해 보겠습니다.
새로운 기능이 정상적으로 작동합니다. 테스트 출력의 시작 부분에서 라이브러리 버전을 확인할 수 있습니다.
그러나 한 가지 문제가 있을 수 있습니다.
라이브러리에서 모듈 교체
현재 라이브러리는 첫 번째 버전이 아닙니다. 두 번째 버전입니다. 버전 번호가 잘못되었습니다. 첫 번째 버전에는 cipher_version() 함수가 없었습니다. 현재 버전에는 존재합니다. 따라서 이 버전은 “0.0.2” 버전이어야 합니다. 라이브러리 내의 cipher_version() 함수를 수정된 함수로 교체해야 합니다.
다행히도 ar 명령어를 사용하면 이 작업을 매우 쉽게 수행할 수 있습니다.
먼저 라이브러리 디렉토리에 있는 cipher_version.c 파일을 수정합니다. “Version 0.0.1 Alphan” 텍스트를 “Version 0.0.2 Alphan”으로 변경합니다. 변경 후 코드는 다음과 같아야 합니다.
#includevoid cipher_version(void) { puts("How-To Geek :: VERY INSECURE Cipher Library"); puts("Version 0.0.2 Alphan"); } // end of cipher_version
수정된 파일을 저장합니다. 새로운 cipher_version.o 오브젝트 파일을 생성하기 위해 다시 컴파일해야 합니다.
gcc -c cipher_version.c
이제 라이브러리에 있는 기존 cipher_version.o 오브젝트 파일을 새로 컴파일된 버전으로 교체합니다.
이전에 라이브러리에 새 모듈을 추가하기 위해 -r 옵션을 사용했습니다. 이미 라이브러리에 존재하는 모듈과 함께 사용하면 ar 명령어는 이전 버전을 새 버전으로 교체합니다. -s 옵션은 라이브러리 인덱스를 업데이트하며, -v 옵션은 ar이 수행한 작업을 자세히 알려줍니다.
ar -rsv libcipher.a cipher_version.o
이번에는 ar 명령어가 cipher_version.o 모듈을 교체했다고 보고합니다. “r”은 “replaced”를 의미합니다.
업데이트된 cipher_version() 함수 사용
수정된 라이브러리를 사용하고 제대로 작동하는지 확인해야 합니다.
라이브러리 파일을 테스트 디렉토리에 복사합니다.
cp libcipher.* ./test
테스트 디렉토리로 이동합니다.
cd ./test
새로운 라이브러리로 테스트 프로그램을 다시 컴파일해야 합니다.
gcc test.c libcipher.a -o test
이제 프로그램을 실행하여 테스트합니다.
./test
테스트 프로그램의 출력이 예상대로 표시됩니다. 버전 문자열에 올바른 버전 번호가 표시되고 암호화 및 복호화 기능도 정상적으로 작동합니다.
라이브러리에서 모듈 삭제
이제는 필요하지 않다고 판단되는 cipher_version.o 파일을 라이브러리 파일에서 삭제해 보겠습니다.
삭제를 위해 -d 옵션을 사용합니다. 또한 -v 옵션을 사용하여 ar 명령어가 수행하는 작업을 확인하고, -s 옵션을 사용하여 라이브러리 파일의 인덱스를 업데이트합니다.
ar -dsv libcipher.a cipher_version.o
ar 명령어가 모듈을 제거했다는 메시지를 출력합니다. “d”는 “deleted”를 의미합니다.
ar 명령어에 라이브러리 파일 내부의 모듈을 나열하도록 요청하면, 모듈 수가 두 개로 줄어든 것을 확인할 수 있습니다.
ar -t libcipher.a
라이브러리에서 모듈을 삭제하려면 라이브러리 헤더 파일에서도 해당 정의를 제거해야 합니다.
코드 공유
라이브러리는 코드를 실용적이면서도 개인적인 방식으로 공유할 수 있게 해줍니다. 라이브러리 파일과 헤더 파일을 제공하면, 실제 소스 코드를 공개하지 않고도 라이브러리를 사용할 수 있습니다.