깃허브에서 받은 프로젝트를 돌려보기 위해서 readme.md를 읽고
프로젝트를 분석해서 라이브러리들을 빌드하고, make를 해봤으나....!!
엄청 긴 에러 로그를 만나게 되었다.
처음엔 원인이 무엇인지도 모르고 뻘짓을 많이 하긴했었지만, 결국 에러 로그는 무언가 잘못한게 있기 때문에 나오는 것이니까 하나하나 파헤쳐보던 중에 알게 된 사실이 있다.

"C언어 makefile에서 라이브러리를 링크하는 과정에서는 그 순서도 정말 중요하다."

그래서 같은 라이브러리를 import 한다고 하더라도 그 순서에 따라서 빌드가 성공할수도, 실패할수도 있는 것이다.

이게 내가 기획하고 처음부터 빌드한 프로젝트면 몰라도 다른 사람이 만든 프로젝트를 다운 받아서 빌드해볼 때는 꽤나 자주 겪을 수 있는 문제라 기록을 남기려고 한다.

 ldd - 실행 파일 또는 공유 라이브러리 파일의 의존성 체크

 

"ldd program_name" 명령어를 실행하면 program_name이 의존하는 라이브러리들을 확인할 수 있습니다. 
일반적인 경우 ldd는 LD_TRACE_LOADED_OBJECTS 환경 변수를 사용하여 표준 동적 링커를 호출합니다.
이렇게 하면 동적 링커가 프로그램의 동적 종속성 및 찾기를 실행하여 이를 충족하는 객체를 로드합니다.

ldd가 a.out 공유 라이브러리에서 작동하지 않는다는 이슈를 확인했습니다.
이는 매우 오래된 일부 a.out 프로그램에서 작동하지 않기 때문에 언급되었던 이슈입니다. ldd 자원이 컴파일러 릴리스에 추가되기 전에 빌드 되었습니다. 

일부 ldd 버전은 종속성 정보를 얻으려고 시도할 수 있습니다.
그렇기 때문에 신뢰할 수 없는 실행 파일에 ldd 를 사용해서는 안됩니다. 보안 이슈가 있습니다.

ldd에도 옵션이 존재합니다.

--version ldd 
              의 버전 번호를 인쇄합니다 .

       -v , --verbose
              예를 들어 기호를 포함한 모든 정보를 인쇄합니다.
              버전 정보.

       -u , --미사용
              사용하지 않는 직접 종속성을 인쇄합니다. (glibc 2.3.4부터.)

       -d , --데이터-재배치
              재배치를 수행하고 누락된 개체를 보고합니다(ELF
              오직).

       -r , --function-relocs
              데이터 개체와 기능 모두에 대한 재배치를 수행합니다.
              누락된 개체 또는 기능을 보고합니다(ELF만 해당).

       --help 사용 정보.

 

https://man7.org/linux/man-pages/man1/ldd.1.html

참조

 

ldd(1) - Linux manual page

ldd(1) — Linux manual page LDD(1) Linux Programmer's Manual LDD(1) NAME         top ldd - print shared object dependencies SYNOPSIS         top ldd [option]... file... DESCRIPTION         top ldd prints the shared objects (shared libraries) r

man7.org

 

 readelf - ELF 파일이 의존하는 라이브러리의 정보 확인

ELF(Executable and Linkable Format)는 실행 파일, 목적 파일, 공유 라이브러리 그리고 코어 덤프를 위한 표준 파일 형식이다. 1999년 86open 프로젝트에 의해 x86 기반 유닉스, 유닉스 계열 시스템들의 표준 바이너리 파일 형식으로 선택되었다. - 위키 백과

ELF 파일은 코드와 데이터를 여러 섹션으로 구분하고, 각 섹션은 읽기 전용, 쓰기 가능 또는 실행 가능 등의 특정 권한을 가질 수 있습니다. 또한 ELF 파일은 컴파일된 코드 및 데이터 외에도 기호(symbol)와 같은 디버그 정보를 포함할 수 있으며, 동적 링크를 지원하기 위해 필요한 링크 섹션을 가지고 있습니다.

ELF에 대해서는 공부하고 정리할게 많아서 추후에 한번 더 정리할 계획이다.

 

 objdump -  GNU 바이너리 유틸리티의 일부

objdump 는 GNU 바이너리 유틸리티의 일부로서, 라이브러리, 컴파일된 오브젝트 모듈, 공유 오브젝트 파일, 독립 실행 파일 등의 바이너리 파일들의 정보를 보여주는 프로그램이다. objdump는 개체 파일 및 실행 파일을 검사하는데 일반적으로 사용되는 command line tool 입니다. objdjump 는 ELF 파일을 어셈블리어로 보여주는 역어셈블러로 사용될 수 있습니다. 역어셈블러는 기계어를 어셈블리어로 변환하는 컴퓨터 프로그램으로서, 리버스 엔지니어링 도구 중 하나로 볼 수 있는데, 이번 포스팅에서는 생략하도록 하겠습니다.

objdump 를 사용해서 얻을 수 있는 이점 중의 일부를 3가지 정도만 언급하겠습니다.

1. 디버깅 : objdump를 사용하여 실행 파일의 내용을 검사하고 기호 누락, 잘못된 재배치 정보 또는 프로그램이 충돌하거나 잘못 작동할 수 있는 기타 문제와 같은 문제를 찾을 수 있습니다.
2. 리버스 엔지니어링  : objdump를 사용하여 바이너리를 분석하고 작동 방식을 학습할 수 있습니다. 이 도구는 프로그램의 제어 흐름을 이해하고 기능과 변수를 식별하며 보안 취약성을 감지하는 데 도움이 될 수 있습니다.
3. 최적화 : objdump는 프로그램의 성능을 분석하고 코드의; 병목 현상을 식별하는 데 사용할 수 있습니다. 디스 어셈블리를 검사하여 자주 실행되는 코드 섹션을 식별하고 속도를 위해 최적화할 수 있습니다.

그러나 objdump 를 사용하면 몇가지 일어날 수 있는 문제도 있습니다.

1. 복잡성 : objdump 는 바이너리 파일에 대한 많은 정보를 제공하지만 이 정보를 해석하는 것은 어려울 수 있습니다. 기본 아키텍쳐 및 어셈블리 언어에 대한 충분한 이해가 필요합니다.
2. 보안 위험 : objdump 는 바이너리를 리버스 엔지니어링 하는 데 사용할 수 있으므로 공격자가 취약성을 식별하거나 바이너리에서 중요한 정보를 추출하는 데 사용할 수도 있습니다.
3. 호환성 : 다른 버전의 objdump는 이진 파일에 대해 다른 정보를 제공할 수 있습니다. 분석 중인 바이너리와 일치하는 올바른 버전의 objdump를 사용하는 것이 중요합니다.

간단한 예제와 함께 실습해보겠습니다.

file1.c

#include <stdio.h>
#include "header.h"

int main() {
   printf("Hello, World!\n");
   printf("The value of a is %d\n", a);
   return 0;
}


header.h

int a;


Bash

$ gcc -c file1.c
$ gcc -c header.h
$ objdump -p file1.o

 

이렇게 빌드하면 다음과 같은 출력이 나타납니다.

file1.o:     file format elf64-x86-64

.......

 

 pkg-config - 컴파일러나 링커에 필요한 라이브러리 정보를 확인

pkg-config는 C/C++ 소프트웨어 개발에서 사용되는 라이브러리 의존성 관리 도구입니다. 이 도구는 라이브러리의 이름, 버전 및 기타 정보를 검색하여 컴파일러 및 링커가 라이브러리를 찾을 수 있도록 도와줍니다. pkg-config는 소프트웨어 패키지 설치를 간편하게 하기 위해 GNU Build System과 함께 자주 사용됩니다.

pkg-config는 각 라이브러리의 .pc 파일을 읽어서 필요한 헤더 파일, 컴파일러 옵션, 링커 옵션 등을 찾아줍니다. 개발자는 pkg-config를 사용하여 필요한 라이브러리를 검색하고, 해당 라이브러리의 정보를 얻을 수 있습니다. 이를 통해 컴파일 및 링크 단계에서 필요한 라이브러리를 찾을 수 있습니다.

pkg-config를 사용하면 의존성이 있는 라이브러리를 찾기가 쉬워집니다. 또한, 여러 라이브러리를 사용할 때 각 라이브러리의 정보를 일일이 찾아볼 필요가 없습니다. 하지만, pkg-config를 사용하면 각 라이브러리의 .pc 파일이 존재해야 하기 때문에, 라이브러리 제작자가 .pc 파일을 작성하지 않았다면 pkg-config를 사용할 수 없습니다. 또한, pkg-config를 사용하면 Makefile 등의 빌드 스크립트를 복잡하게 만들 수 있습니다.

pkg-config는 3가지 정도의 사용법이 있습니다.

1. 라이브러리 설치 여부 확인
    pkg-config --exists <library-name>
    이 명령은 지정된 라이브러리가 설치된 경우 종료상태 0을 반환하고 그렇지 않은 경우 0이 아닌 값을 반환합니다.

2. 라이브러리에 대한 컴파일러 및 링커 플래그 검색
    pkg-config --cflags --libs <library-name>
    이 명령은 지정된 라이브러리에 대해 컴파일 및 링크하는 데 필요한 컴파일러 및 링커 플래그를 출력합니다.

3. Makefile 에서 pkg-config 사용 

    LIBS = `pkg-config --libs <library-name>`
    CFLAGS = `pkg-config --cflags <library-name>`
    
    myprogram: myprogram.c
        gcc $(CFLAGS) -o myprogram myprogram.c $(LIBS)

이런 식으로 Makefile 의 LIBS, CFLAGS 등의 변수에 pkg-config를 사용해서 make를 사용해서
링커 플래그를 검색하는 과정에서 .c 파일들을 컴파일 및 링크할 때 사용할 수 있습니다.


대표적으로 C언어에서 각 파일 간의 의존성을 체크하는 방법 들 중 4가지를 체크해보았습니다.

의존성을 체크할 때 상황에 맞는 명령, 도구를 사용해서 의존성을 체크하고 프로젝트를 빌드할 수 있도록 합시다.

'코딩이야기 > C' 카테고리의 다른 글

[c] Makefile과 make에 대해서 -1-  (0) 2023.03.17

+ Recent posts