2011. 7. 3. 22:34

♧ strings는 바이너리 파일에서 문자열을 추출하기 위한 툴로서, GNU Binutils에 포함.

▷ strings를 이용하면 에러 메시지 검색 등 프로그램을 간단하게 분석해 볼 수 있음.

   

[ strings 사용법 ]

strings의 기본적인 사용법은 간단하다.

문자열을 추출하고자 하는 바이너리 파일을 인수로 넘기기만 하면 된다.

바이너리 파일은 /bin/ls와 같은 실행 파일이나

foo.jpg, bar.mp3와 같은 임의의 바이너리 파일 모두 가능.

   

▷ 표준입력으로부터 바이너리 데이터를 읽어 들여서 처리할 수도 있다.

strings는 grep명령과 함께 사용하면 편리하다.

다음 예에서는 'ignoring'이 포함된 에러 메시지를 검색 한다.

   

▷ 『-t radix 』 :: 문자열 앞에 파일의 오프셋을 출력

radix :: "x"-16진수, "d"-10진수, "o"-8진수

   

   

[ 문자열 판별 ]

▷ strings는 기본적으로 「ASCII 문자(7비트)로 구성된 4바이트 이상의 표현 가능한 문자열」

이라는 규칙으로 문자열을 판별한다. 이 때문에 UTF-8로 프로그램에 삽입해 놓은

한글 (8비트)은 기본적으로 출력되지 않는다.

   

const char *p = "한글 메시지 입니다.";

▷ UTF-8문자열을 출력하려면 strings 명령에 -eS 옵션을 사용한다.

-e는 인코딩, S는 8비트라는 의미.

단, -eS를 사용하면 알 수 없는 문자열도 많이 출력되므로 -n 옵션으로

출력에 필요한 바이트 수(기본 값 4)를 늘려서 출력될 문자열을 조절할 필요가 있다.

% strings -eS test

   

   

[ 오브젝트 파일 처리 ]

▷ strings는 내부적으로 BFD 라이브러리가 해석 가능한 오브젝트 파일

(실행 파일 또는 라이브러리 등)인지를 판별해서 오브젝트 파일인 경우에는

데이터 섹션만을 문자열 추출 대상으로 하게 된다.

이는 다음과 같이 C 프로그램에 포함된 문자열은 일반적으로 데이터 섹션에 포함되기 때문이다.

const char *p = "hello, world";

   

오브젝트 파일이 아닌 경우는 항상 파일 전체를 대상으로 문자열을 추출한다.

또한 표준입력으로부터 읽어 들인 경우 BFD로 판별할 수 없기 때문에,

이런 경우도 파일 전체를 대상으로 하게 된다.

[ -a | --all | - ]옵션 :: 강제적으로 파일 전체를 대상으로 검색.

[ -T ]옵션 :: 시스템 표준 외의 오브젝트 파일 형식을 지정.

  

  

Posted by devanix
2011. 7. 3. 05:13

♧ 프로그램 실행에 필수적인 데이터를 파일에서 읽어 들여 이용할 때가 있다.

이 방법을 이용하면 손쉽게 데이터를 갱신할 수 있다는 이점이 있지만,

단일 실행 파일로 실행할 수 없고 데이터 파일을 분실하게 될 문제점도 있다.

여기서는 objcopy를 이용해서 실행파일에 데이터를 삽입하는 방법을 소개 한다.

   

[ 데이터 삽입 ]

적은 데이터를 소스코드에 삽입하는 것은 간단하다.

소스코드에 들어 있는 "Hello, world" 등의 메시지는 소스코드에 삽입된 데이터라 할 수 있다.

한편, 영화나 사전 등의 거대한 데이터를 소스코드에 삽입하기란 그리 간단하지 않다.

우선 데이터를 문자열 등으로 변환할 필요도 있고, 변환 후의 거대한 소스코드는 컴파일러가

처리할 수 있는 크기를 초과할 가능성이 높다.

   

[ objcopy ]

그래서 등장한 것이 GNU binutils에 포함되어 있는 objcopy 명령이다.

objcopy를 사용하면 임의의 파일을 링크 가능한 오브젝트 파일로 변환할 수 있다.

   

예) foo.jpg를 x86용 ELF32 형식의 오브젝트 파일 foo.o로 변환하려면 다음과 같다.

% objcopy -I binary -O elf32-i386 -B i386 foo.jpg foo.o

   

▷ foo.o를 링크한 C 프로그램에서 foo.jpg 데이터는 다음과 같은 변수명을 이용해 참조.

extern char _binary_foo_jpg_start[];

extern char _binary_foo_jpg_end[];

extern char _binary_foo_jpg_size[];

   

▷ 이 변수들을 다음과 같이 사용한다.

const char *start = _binary_foo_jpg_start; // 데이터의 시작 주소 얻기

const char *end = _binary_foo_jpg_end; // 데이터의 끝 주소 +1 얻기

int size = (int) _binary_foo_jpg_size; // 데이터 크기 얻기

   

마지막의 _binary_foo_jpg_size 변수는 &_binary_foo_jpg_size[0]이 주소가 아닌

값(데이터 크기) 이므로 주의해야 한다.

   

[ 따라 해보자 ]

① jpg파일을 오브젝트 파일로 변환

② 간단한 test.c 작성

#include <stdio.h>

   

extern char _binary_foo_jpg_start[];

extern char _binary_foo_jpg_end[];

extern char _binary_foo_jpg_size[];

   

int main() {

  printf("start address : %p\n", _binary_foo_jpg_start);

  printf("end address : %p\n", _binary_foo_jpg_end);

  printf("size : %d\n", (int)_binary_foo_jpg_size);

   

  return 0;

}

③ 변환된 .jpg 오브젝트 파일을 링크하여 컴파일 후 출력

   

   

[ 정리 ]

objcopy를 이용해 실행 파일에 데이터를 삽입하는 방법을 소개했다.

여기서 살펴본 예와 같은 일반 데이터뿐만 아니라 프로그램 자체의 소스코드나

다른 프로그램의 바이너리와 같이 특이한 데이터를 삽입하는 것과 같은 재미있는

경우도 있을 수 있다.

   

Posted by devanix
2011. 7. 3. 02:54

[ objdump를 이용한 오브젝트 파일 역어셈블 ]

 

♧ 역어셈블 관련 옵션

옵션 [ 긴 옵션 ]

설명

-d | --disassemble

오브젝트 파일을 기계어로 역어셈블 (실행 코드가 있는 섹션)

-D | --disassemble-all

모든 섹션을 대상으로 역어셈블

--[no-]show-raw-insn

코드와 바이트열 제거/출력 [디폴트=보여줌]

--prefix-address

코드의 주소를 심볼에서의 상대주소로 표시

-j section | --section=section

특정 섹션 지정

-l | --line-numbers

각각의 코드에 대응하는 소스코드의 행에 관한 정보 출력.

-S | --source

행 번호에 해당하는 소스코드가 그 위치에 삽입되어 출력.

   

   

▶ -d 옵션을 사용한 일반적인 역어셈블

▷ -d 옵션을 사용해 역어셈블 할 때는 통상 실행 코드가 있는 섹션(.text등)을 역어셈블 대상으로 삼는다.

   

▶-D 옵션을 사용해 모든 섹션을 대상으로 역어셈블

길어서 pass~

   

▷ 일반적인 역어셈블 결과는 다음과 같은 형식으로 출력.

주소 <심볼>:

  주소: 코드의 바이트열 역어셈블 코드

   

▶ 『--no-show-raw-insn』 옵션을 사용하여 코드의 바이트 제거하여 출력.

   

▶ 『--prefix-address』 옵션을 사용하여 코드 주소를 심볼에서의 상대 주소로 표시

   

▷ 『--prefix-address』 옵션을 사용하면 디폴트로 코드의 바이트열은 출력되지 않음으로,

출력하고자 할 경우에는 『--show-raw-insn』 옵션을 함께 사용.

   

▶ 덤프할 때와 마찬가지로 -j 옵션을 사용하여 특정 섹션 지정 출력

   

▶ 덤프할 때와 마찬가지로 주소 범위 지정 출력

전 챕터(Hack #9)에서 했으므로 패스~

   

▶ -l옵션을 사용하여 가각의 코드에 대응하는 소스코드의 행에 관한 정보도 출력.

   

▶ -S옵션을 사용하여 -l 옵션의 행 번호에 대응하는 소스코드를 삽입하여 출력.

▷ -S옵션과 -l옵션 동시 사용 가능

▷ -S옵션과 -l옵션은 오브젝트 파일에 디버그 정보가 포함되어 있어야 함. (gcc –g)

   

▷ 오브젝트 파일의 디버그 정보로는 소스코드 경로명과 행 번호만 포함되므로

해당 경로에 소스 파일이 존재해야 한다. 만약 소스 파일이 없다면 소스는 출력되지 않으며

다른 소스 파일이 존재한다면 엉뚱한 소스가 출력될 것이다.

▶ 소스 파일명을 변경후, -S옵션을 사용하였음에도 소스코드는 출력 되지 않음.

   

   

▷ 링크하기 전의 오브젝트 파일에서는 재배치되는 주소가 0임에 주의해야 한다.

▷ 위 예에서는 "Hello World!!!\n"을 가리키는 포인터는 12~15 사이의

4바이트에 채워져 있어야 하지만, 링크하기 전이므로 0인 채로 남아 있다.

   

▶ 링크 후 생성된 실행 파일에서는 해당하는 부분에 다음과 같이 주소가 채워져 있다.

  

 

Posted by devanix
2011. 7. 3. 02:12

[ 일반적인 ELF 바이너리 덤프 ]

『 -s | --full-contents 』 :: 모든 섹션의 전체 내용을 표시.

   

▶ 일반적인 덤프.

# objdump -s /bin/ls

▷ 덤프 형식 :: [메모리 주소] [16진수 덤프 (4바이트 * 4)] [ASCII 표시]

▷ 여기서 16진수 덤프는 x86과 같은 리틀 엔디안 아키텍처에서 실행해도 빅 엔디안으로 출력.

▷ objdump에는 -endian 옵션이 있지만 이 옵션은 단지 디스어셈블할 경우에만 영향을 줄 뿐,

출력 시에는 영향을 주지 않는다.

▷ 통상 elf32-i368이므로 섹션 별로 식별해서 덤프 하게 된다.

   

▷ 오브젝트 형식은 -b 옵션 (--target옵션)으로 지정.

▷ 지정 가능한 오브젝트 형식은 -i 옵션 (--info 옵션)으로 확인.

...(중략)...

  

   

[ ELF 바이너리의 특정 섹션 덤프 ]

♧ 『 -j section | --section=section 』 :: 특정 섹션을 지정하여 덤프

   

▶ 특정 섹션 지정하여 덤프

# objdump -s -j .interp /bin/ls

   

▷ 어떤 섹션이 존재 하는지 확인하기 위해 -h 옵션(--sction-headers, --headers)으로 출력.

...(중략)...

   

[ 주소 범위를 지정하여 덤프 ]

♧ 『 --start-address=address 』 『 --stop-address=address 』 ::

덤프할 주소 범위를 지정하여 덤프.

   

▶ 0x8048154 ~ 0x80481bc까지 주소 지정하여 덤프.

# objdump -s --start-address=0x8048154 --stop-address=0x80481bc /bin/ls

  

   

[ objdump로 바이너리 덤프 ]

♧ 『-b bfdname | --target=bfdname』 :: ELF 형식이 아닌 파일 또는 ELF 파일을

그저 바이너리 파일로 덤프 하고자 할 경우에는 오브젝트 형식으로 binary를 지정.

   

# objdump -s -b binary /etc/ld.so.cache

...(중략)...

▷ 이런 경우 주소는 파일 오프셋과 같다.

▷ binary로 지정하면 섹션별로 나눠어서 출력되지 않는다.

▷ binary 형식은 자동 인식되지 않으므로 반드시 옵션을 지정.

  

Posted by devanix
2011. 7. 2. 18:44

♧ readelf는 BFD 라이브러리를 이용하지 않고 직접 ELF를 읽기 위한 툴.

▷ BFD에 의존적이지 않은 프로그램도 존재하므로 ELF 파일의 문제인지 BFD의 문제인지 쉽게 구분.

▷ readelf는 BFD를 경유하지 않고 ELF 파일을 읽어내므로 objdump보다 상세한 정보를 얻을 수 있다.

▷ 예를 들면 DWARF 디버그 정보를 검사할 수도 있다.

▷ readelf 명령은 하나 이상의 옵션을 사용해야 함. (옵션을 지정하지 않으면 기본 사용법 출력)

(※ BFD 라이브러리(Birnary Descriptor Library):

다양한 형식의 오브젝트 파일의 호환성을 위한 GNU 프로젝트의 주 매커니즘.)

   

 

[ ELF 헤더 출력 ]

헤더 종류

옵션

긴 옵션

ELF 파일 헤더

-h

--file-header

프로그램 헤더

-l (엘)

--program-headers, --segments

섹션 헤더

-S

--section-headers, --sections

위 세가지 헤더

-e

--headers

 

 

[ ELF 정보 출력 ]

종보 종류

옵션

긴 옵션

심볼 테이블

-s

--syms, --symbols

재배치 정보

-r

--relocs

동적 세그먼트

-d

--dynamic

버전 섹션

-V

--version-info

아키텍처 의존정보

-A

--arch-specific

버킷 리스트 길이 히스토그램

-I(아이)

--histogram

모든 헤더 및 정보

-a

--all

코어 노트(core notes)

-n

--notes

unwind 정보

-u

--unwind

▷ 일반적으로 심볼 정보는 심볼 섹션에 있는 심볼 정보를 이용하지만,

-D 옵션 (--use-dynamic 옵션)을 사용하면 동적 섹션에 있는 심볼 정보를 이용하게 됨.

   

   

[ ELF 섹션 덤프 ]

-x 옵션 (--hex-dump 옵션)을 이용해 지정한 섹션의 내용을 덤프.

(섹션은 섹션 번호로 표시하고, -S 옵션으로 출력된 섹션 헤더에 섹션 번호가 표시된다.)
 

▶ 사용 예)

  

 

 

[ DWARF2 디버그 섹션 출력 ]

-w 옵션(--debug-dump 옵션)으로 DWARF2 디버그 섹션 정보를 출력.

-w

--debug-dump=

섹션

l

line

.debug_line

i

info

.debug_info

a

abbrev

.debug_abbrev

p

pubnames

.debug_pubnames

r

aranges

.debug_aranges

R

Ranges

.debug_ranges

m

macro

.debug_macinfo

f

frames

.debug_frame

F

frames-interp

.debug_frame

s

str

.debug_str

o

loc

.debug_loc

 

[ 긴 이름의 심볼 출력 ]

-W 옵션 (--wide 옵션)을 사용하면 80문자 이상의 행도 출력이 가능

(기본적으로 긴 이름의 심볼은 1행 이내에 출력되도록 뒷부분이 잘린다.)

Posted by devanix
2011. 7. 2. 16:47

♧ 공유 라이브러리 의존관계란,

실행 파일 또는 공유 라이브러리에서 필요로 하는 공유 라이브러리의 SONAME이

ELF 정보 내의 동적 섹션에 있는 NEEDED에 기록된 정보로 관리되고 있음을 의미한다.

▷ 개별 파일 내의 NEEDED는 objdump 또는 readelf를 사용해서 확인할 수 있지만,

의존관계를 이루고 있는 모든 공유 라이브러리를 출력하려면 ldd명령을 이용한다.

▷ ldd 명령은 내부적으로 환경변수 LD_TRACE_LOADED_OBJECTS를 이용해 구현되어 있다.

   

   

[ objdump 와 readelf로 공유 라이브러리 의존 관계 확인 ]

♧ 공유 라이브러리를 이용할 때, 실행 파일 또는 공유 라이브러리 자체는

이를 실행 하려 할 때 필요한 공유 라이브러리에 대한 정보를 갖고 있다.

▷ 그 정보는 ELF 동적 섹션의 NEEDED에 기록.

   

▶ [/bin/ls]를 objdump/readelf 명령을 사용하여 확인

objdump -p ::

   

readelf -d ::

▷ 이와 같이 /bin/ls는 [librt.so.1, libselinux.so.1, libacl.so.1, libc.so.6]

4개의 공유 라이브러리를 필요로 함.

▷ 그러나 /bin/ls 실행할 때 이 4개의 공유 라이브러리만 필요한 것은 아니다.

(4개의 공유 라이브러리는 각각의 또 다른 공유 라이브러리를 필요로 하기 때문.)

   

NEEDED로 표시되 어 있는 것은 SONAME이므로, SONAME으로부터 실제 파일을 찾아야 한다.

▷ 특히 설정되어 있지 않은 경우에는

/usr/lib(또는 /lib)에 SONAME에 해당하는 파일이 공유 라이브러리다.

(실제로는 /lib/tls 또는 /lib/tls/i686/cmov 등에 있는 공유 라이브러리가 사용 되기도 함)

▷ 환경변수 LD_LIBRARY_PATH에 라이브러리 경로를 설정한 경우 해당 디렉토리를 참조.

/etc/ld.so.cache에 기록 정보를 참조.

(/etc/ld.so.conf의 설정을 이용해 ldconfig를 실행할 때 갱신)

▷ 예를 들어 [ librt.so.1 ]의 경우, /lib/librt.so.1이라는 파일(심볼릭 링크)이 존재하므로,

이것이 SONAME librt.so.1에 해당하는 공유 라이브러리 파일이다.

   

▶ [ librt.so.1 ]이 필요로 하는 라이브러리 확인.

...(중략)...

   

▶ [ /bin/ls ]의 다른 공유 라이브러리도 의존 관계를 확인.

(※ 공유 라이브러리 의존관계는 CPU종류, OS 배포판에 따라 조금씩 다르다.)

   

   

[ ldd로 공유 라이브러리 의존 관계 확인 ]

♧ 앞에서 살펴본 바와 같이 objdump / readelf를 사용해 공유 라이브러리 의존 관계를 확인할 수는

있지만, 각각의 공유 라이브러리에 대한 의존관계를 개별적으로 확인해야 하므로 다소 번거롭다.

또한 실제 실행될 때 사용되는 공유 라이브러리의 디렉토리 경로를 정확히 얻기도 어렵다.

 

▶ ldd로 공유 라이브러리 의존 관계 확인.

(※ ldd는 실행 파일 외에도 공유 라이브러리에도 사용 가능)

- 이와 같이 실행파일이 필요로 하는 공유 라이브러리의 SONAME, 경로명과 할당된 메모리 주소를 확인.

 

▷ 사실 GNU/리눅스에서 ldd 명령은 단순히

환경변수(LD_TRACE_LOADED_OBJECTS)를 이용한 셸 스크립트이다.

▷ LD_TRACE_LOADED_OBJECTS에 1을 설정해서 프로그램을 실행하면 프로그램 실행 시점에

ELF 인터프리터(런타임 로더 /lib/ld_linux.so.2)가 필요한 공유 라이브러리를 찾아

메모리에 로딩해서 그 정보를 표시한 후, 실제 프로그램이 실행되기 전에 종료하게 된다.

 

▶ LD_TRACE_LOADED_OBJECTS를 이용한 공유 라이브러리 의존성 확인.

- 이와 같이 ldd와 동일한 결과를 얻을 수 있다.

   

▷ LD_TRACE_LOADED_OBJECTS는 실행 파일이 아닌 공유 라이브러리에의 경우 실행할 수 없다.

   

▷ 이런 경우에는 런타임 로드 /lib/ld-linux.so.2를 실행한다.

  

Posted by devanix
2011. 7. 2. 13:24

[ 정적 라이브러리 (static library) ]

♧ 정적 라이브러리는 여러 프로그램에서 사용되는 함수를 포함하는

오브젝트 파일을 하나의 파일로 다룰 수 있도록 정리해 놓은 것.

   

▷ 프로그램을 작성할 때는 소스 파일을 분류해서 일정한 분량으로 나누어

각각의 오브젝트 파일로 컴파일 한 후, 이를 최종적으로 링크해서 하나의 실행 가능한 파일을 생성한다.

이때 다른 프로그램에서도 사용될 만한 모듈이 여러 개의 오브젝트 파일로 나뉘어 있으면

이것들을 한 덩어리로 다루기가 번거로워진다. 그래서 생각해낸 것이 아카이브 파일이다.

   

▷ archive(아카이브) 파일(.a) - 여러 개의 오브젝트 파일을 하나의 파일로 모아놓은 것.

▷ ar(1) 명령을 사용해 여러 개의 오브젝트 파일을 하나의 아카이브 파일로 합칠 수 있다.

▷ OS에 따라 ranlib(1)를 사용하면 이 아카이브 파일 내의 오브젝트가 제공하는 심볼 정보를 해시화해서,

심볼을 제공하는 오브젝트 파일을 효율적으로 검색할 수 있게 된다. (ar -s와 같음)

이와 같은 아카이브 파일을 정적 라이브러리라 한다.

   

▶ 정적라이브러리 생성

ⓐ 오브젝트 파일 생성.

ⓑ 아카이브 파일로 묶어 줌 / 확인.

ⓒ 실행 가능한 파일 생성

▷ 정적 라이브러리를 링크할 경우,

링커는 다른 오브젝트 파일에서 정의되지 않은 심볼을 찾아 지정된 정적 라이브러리에서

해당 심볼을 정의하고 있는 오브젝트 파일의 사본을 추출해서 실행 파일 내에 포함.

▷ 이 경우에는 baz.o 내에 정의되지 않은 심볼이

libfoo.a에 포함되어 있는 오브젝트 파일들 중에 정의되어 있다면,

그 오브젝트 파일을 찾아 실행 파일 baz에 복사해서 링크 한다.

   

▷ 여기서 핵심은 라이브러리 내의 오브젝트 파일 단위로 처리된다는 점,

링크 시에는 실행 파일 내에 오브젝트 파일의 사본이 포함된다는 점.

▷ 정적 라이브러리를 링크해서 생성된 실행 바이너리를 실행할 경우에는

정적 라이브러리가 없어도 관계 없다. 필요한 코드는 실행 바이너리에 복사되어 포함되기 때문.

   

[ 공유 라이브러리 (shared library) ]

♣ 정적 라이브러리의 경우 여러 오브젝트 파일의 아카이브였다면, 공유 라이브러리는

여러 오브젝트 파일을 하나의 거대한 오브젝트 파일로 만들어 이를 공유할 수 있도록 한 것.

- OS의 가상 메모리 관리 시스템이 진보함에 따라, 하나의 파일을 mmap(2)등을 이용해

여러 프로세스에서 메모리를 공유해서 참조할 수 있게 되었다.

이를 활용할 수 있도록 한 것이 공유 라이브러리(or 공유 오브젝트)이다.

(최근의 OS에서는 일단 메모리맵을 설정해 두는 것만으로 실제로 그 메모리 내용이 참조되기까지

디스크 액세스를 지연시킬 수 있으므로, 거대한 오브젝트 파일이라도 그다지 문제가 되지 않는다.)

   

▶ 공유 라이브러리 생성

-shared옵션 : 공유 오브젝트를 만들게 된다.

-Wl,-soname옵션 : 공유 오브젝트에 특정 SONAME을 지정.

(SONAME에 따라 실행 시에 링크할 공유 라이브러리가 결정)

   

▷ 공유 라이브러리는 정적 라이브러리와 같은 방법으로 링크 할 수 있다.

- 그러나 실제 내부적인 처리는 많이 다르다.

- 이 경우 baz.o 내에 정의되지 않은 심볼이 공유 오브젝트에 정의되어 있다면,

해당 공유 오브젝트의 SONAME을 실행 파일의 NEEDED에 설정할 뿐,

공유 오브젝트에 포함되어 있는 코드 자체를 복사하지는 않는다.

(정적 라이브러리와 달리 *.so 내에 어떤 오브젝트 파일이 있는지는 기본적으로 남지 않는다)

- 여기서 핵심은 공유 라이브러리 단위로 처리된다는 점,

링크 시에는 필요한 공유 라이브러리의 SONAME만을 실행 파일에 NEEDED로 등록해 둔다는 점이다.

- 공유 라이브러리를 링크한 실행 파일을 실행할 경우에는 동적 링커 로더가

NEEDED의 정보를 이용해 필요한 공유 라이브러리를 찾아내어,

실행 시에 해당 프로세스의 메모리맵을 조작해서 공유 라이브러리를

링크한 실행 파일을 실행할 경우에는 공유 라이이브러리가 시스템에 반드시 존재해야 한다.

실제 라이브러리 코드는 실행 파일에 포함되어 있지 않고 공유 라이브러리에만 존재.

(※ /etc/ld.so.conf 수정, ldconfig)

   

   

[ 라이브러리 차이점]

♣ 메모리 크기

▷ 실행 시에 필요한 메모리 크기도, 최근의 OS에서는 공유 라이브러리 쪽이 유리.

▷ 특히 PIC(Position Independent Code)로 생성해 두면 코드 부분이 어느 주소에 위치하더라도

변경할 필요가 없기 때문에, 공유 라이브러리를 하나의 물리적인 메모리 페이지에 읽어 들이는 것만으로

각각의 메모리 공간에 있는 프로세스에서 공유 라이브러리의 메모리 페이지를 공유 할 수 있게 된다.

 

♣ 파일 크기

정적 라이브러리:

라이브러리에 포함된 코드를 여러 실행 파일에서 이용하게 되면

각 오브젝트가 매번 복사되므로 용량이 늘어나게 됨.

공유 라이브러리:

라이브러리 코드 자체는 실행 파일에 복사되지 않고 공유 라이브러리 파일에서만

포함하고 있으므로, 라이브러리 코드를 이용하는 실행 파일이 많더라도

라이브러리 코드 크기만큼의 용량이 증가하지 않음.

    

♣ 라이브러리 패치 (문제가 되는 코드 발견시)

정적 라이브러리:

컴파일된 실행 바이너리에 복사된 정적 라이브러리의 오브젝트가 남아 있으므로,

해당 라이브러리를 사용하는 모든 실행 바이너리를 재컴파일 해야 함.

공유 라이브러리:

공유 라이브러리만 수정하면 됨.

데몬과 같이 장시간 실행하고 있는 프로그램의 경우, 메모리에 로드 되어 있으므로

새로운 공유 라이브러리를 참조하도록 재실행 해야 함.

 

  

Posted by devanix
2011. 7. 1. 15:50

♧ ELF :: Executable and Linking Format 의 약자로,

실행 가능한 바이너리 또는 오브젝트 파일 등의 형식을 규정한 것.

▷ ELF 파일은 ELF헤더가 맨 앞에 위치.

▷ 프로그램 헤더 테이블과 섹션 헤더 테이블이 그 뒤에 위치.

▷ 이러한 헤더의 구조는 elf.h 참조

   

   

   

:: ELF 에서 사용하는 자료형

▷ ELF 바이너리에는 32bit와 64비트, 두 가지가 있다.

자료형

N = 32

N = 64

설명

ElfN_Half

uint16_t

uint16_t

부호 없는 16비트 값

ElfN_Word

uint32_t

uint32_t

부호 없는 32비트 값

ElfN_Sword

int32_t

int32_t

부호 있는 32비트 값

ElfN_Xword

uint64_t

uint64_t

부호 없는 64비트 값

ElfN_Sxword

int64_t

int64_t

부호 있는 64비트 값

ElfN_Addr

uint32_t

uint64_t

주소

ElfN_Off

uint32_t

uint64_t

오프셋

ElfN_Section

uint16_t

uint16_t

섹션 인덱스

ElfN_Versym

uint16_t

uint16_t

버전 심볼 정보

  

   

   

:: [ ELF 헤더 ] readelf 의 -h옵션 (--file-header)

▷ ELF헤더는 ELF파일 맨 앞에 반드시 존재하며, 그 파일이 ELF 파일임을 나타냄.

   

[ ELF헤더 구조 ] (/usr/include/elf.h)

   

▷ e_ident::

ELF의 매직 넘버(MAGIC NUMBER)와 기타 정보를 갖고 있다.

ELF 파일은 첫 4바이트에 다음과 같은 매직 넘버를 갖는다

| 0x7F | 0x45 | 0x4c | 0x46 | → "\177ELF"가 된다.

그 다음 바이트는 32비트인 경우 ELFCLASS32(1), 64바이트인 경우 ELFCLASS(2)가 된다.

다음 바이트는 엔디안을 나타냄.(리틀 엔디안인=ELFDATA2LSB(1),빅 엔디안인=ELFDATA2MSB(2))

나머지 바이트는 ELF버전과 OS, ABI 등의 정보를 1바이트씩을 사용해 나타냄.

   

▷ e_type:: 다음 타입 중 하나를 나타낸다.

타입

설명

ET_REL

1

재배치 가능한 파일

ET_EXEC

2

실행 가능한 파일

ET_DYN

3

공유 오브젝트 파일

ET_CORE

4

코어 파일

   

▷ e_machine:: 아키텍처 타입을 타나낸다. (EM_으로 시작하는 상수로 정의되어 있음.)

▷ e_version:: ELF 버전을 나타낸다. (위 예(/bin/ls)는 EV_CURRENT(1)이다)

▷ e_entry:: 이 ELF에서 실행 시작하는 가상 주소.

▷ e_ehsize:: ELF 헤더 자체의 크기.

▷ e_phoff, e_phentsize, e_phnum:: 프로그램 헤더 테이블의 위치, 크기, 헤더 개수.

▷ e_shoff, e_shentsize, e_shnum:: 섹션 헤더 테이블의 위치, 크기, 개수.

▷ e_shstrndx:: 섹션명의 스트링 테이블을 갖는 섹션 헤더 인덱스를 나타냄.

   

   

:: [ 프로그램 헤더 ] readelf의 -l 옵션 (--program-headers)

▷ 프로그램 헤더 테이블은 ELF 헤더의 e_phoff로 지정된 오프셋에서 시작.

▷ e_phentsize(헤더크기)와 e_phnum(프로그램 헤더 개수)으로 정해진 크기를 갖는 테이블.

(※ 즉, 프로그램 헤더 테이블의 전체 크기 = e_phentsize * e_phnum)

   

 [ 프로그램 헤더 구조 ]

   

▷ 『Program Headers:』의 각 행에 해당.

▷ p_type:: 다음과 같다.

p_type

설명

PT_LOAD

1

로드된 프로그램 세그먼트

PT_DYNAMIC

2

동적 링크 정보

PT_INTERP

3

프로그램 인터프리터

PT_NOTE

4

추가 정보

PT_PHDR

6

프로그램 헤더 테이블 자신

PT_TLS

7

스레드 지역 저장소

PT_GNU_EH_FRAME

0x6474e550

GNU .eh_frame_hdr 세그먼트

PT_GNU_STACK

0x6474e551

스택 실행 가능성

   

▷ 『Section to Segment mapping:』 이후의 행에 나타난 정보는

『Program Headers:』의 각 프로그램 헤더에 나타난 세그먼트에

그 세그먼트 메모리 범위를 포함하는 섹션명을 나열하고 있다.

   

(인덱스 00) 프로그램 헤더에 표시된 세그먼트는 PHDR 타입이고 그에 속하는 섹션은 없다.

(인덱스 01) 프로그램 헤더에 표시된 세그먼트는 INTERP 타입이고 그에 속하는 섹션은 .interp가 있다.

(인덱스 02) 프로그램 헤더에 표시된 세그먼트는 LOAD 타입이고 그 안에는 .interp, .note.ABI-tag, ...등등

▷ 『Section to Segment mapping:』 ↔ 『Program Headers:』 이 둘을 연결해서 봄.

type

segment

section

PHDR

00

없음.

INTERP

01

.interp

LOAD

02

.interp , .note.ABI-tag , .note.gnu.build-id, .hash ...[등등]

  

   

   

:: [ 섹션 헤더 ] readelf의 -S 옵션 (--section-headers)

▷ 섹션 헤더 테이블은 ELF 헤더의 e_shoff로 지정된 오프셋에서 시작.

▷ e_shentsize(섹션 헤더 크기)와 e_shnum(섹션 헤더 개수)으로 정해진 크기를 갖는 테이블.

(※ 즉, 섹션 헤더 테이블의 전체 크기 = e_shentsize * e_shnum)

   

[ 섹션 헤더 구조 ]

▷ 『Section Headers:』의 각 행에 해당.

▷ 섹션명(Section name)은

ELF 헤더의 e_shstrndx로 지정된 섹션에 포함된 스트링 테이블의 인덱스로 지정.

▷ 위의 /bin/ls 예의 경우,

e_shstrndx는 28이므로, 28번째 섹션 헤더가 그 스트링 테이블을 갖는 섹션이 된다.

- sh_offset이 '0x0192d4' 이고, 크기가 '0x0000f2' 바이트인 스트링 테이블(STRTAB)임을 알 수 있다.

   

▷ 섹션 타입(sh_type):

 섹션 타입

 값 

 설명

 SHT_PROGBITS

 1

 프로그램 데이터

 SHT_STRTAB

 2

 심볼 테이블

 SHT_STRTAB

 3

 스트링 테이블

 SHT_RELA

 4

 재배치 엔트리

 SHT_HASH

 5

 심볼 해시 테이블

 SHT_DYNAMIC

 6

 동적 링크 정보

 SHT_NOTE

 7

 Notes (호환 체크정보)

 SHT_NOBITS

 8

파일상에  데이터가 없는 부분 (.bss)

 SHT_REL

 9

 재배치 엔트리

 SHT_DYNSYM

 11

 동적 링크의 심볼테이블

 SHT_INIT_ARRAY

 14

 생성자 배열 (.init) 

 SHT_FINI_ARRAY

 15

 소멸자 배열 (.fini)

 SHT_GNU_verdef

 0x6ffffffd

 버전 정의 섹션

 SHT_GNU_verneed 

 0x6ffffffe

 필요한 버전 섹션

 SHT_GNU_versym

 0x6fffffff

 버전 심볼 테이블

  

   

   

:: [ 스트링 테이블 ]

▷ 스트링 테이블은 단순한 문자열 리스트이다.

▷ /bin/ls의 경우 다음 섹션이 스트링 테이블이다.

▷ 예를 들어 [28] .shstrtab의 오프셋은 '0x0192d4', 크기는 '0xf2'이므로

od를 사용하여 테이블을 확인하자.

# od --skip-bytes 0x192d4 --read-bytes 0xf2 -t x1z /bin/ls

▷ 이 경우 스트링 테이블은 다음과 같이 되어 있다.

인덱스

문자열

1

.shstrtab

11

.interp

19

.note.ABI-tag

즉, .shstrtab의 맨 앞부터의 오프셋이 스트링 테이블의 인섹스가 된다.

   

   

:: [ 심볼 테이블 ] readelf의 -s 옵션 (--syms)

▷ 심볼 테이블은 심볼과 그 값 등을 대응시키는 테이블이다.

▷ /bin/ls의 경우 strip되어 있으므로 동적 심볼 테이블만 있다.

...(중략)...

▷ 이를 ELF 헤더로부터 찾아 보자.

우선 섹션 헤더에서 .dynsym이라는 심볼 테이블이 있다는 것을 알 수 있다.

▷ 덤프해 보면 다음과 같다.

...(중략)...

▷ 심볼 테이블은 다음과 같은 구조 테이블이다.

32비트와 64비트 ELF 바이너리에서는 st_value의 정렬 순서에 따라 변한다.

▷ st_name=스트링 테이블의 인덱스, st_value=심볼 값, st_size=심볼크기.

▷ st_info의 하위 4비트는 심볼 테이블 등의 정보로 다음과 같은 것이 있다.

심볼 타입

설명

STT_OBJECT

1

데이터 오브젝트

STT_FUNC

2

실행 코드

STT_SECTION

3

섹션 관련

STT_FILE

4

오브젝트 관련 소스코드 파일

STT_COMMON

5

일반 데이터

STT_TLS

6

스레드 지역 저장 데이터

▷ 또한 st_info의 상위 4비트는 그 심볼의 바인딩 방법을 타나낸다.

심볼 바인딩

설명

STB_LOCAL

0

지역 심볼

STB_GLOBAL

1

전역 심볼

STB_WEAK

2

WEAK

   

▷ st_shndx는 관련된 섹션을 나타낸다.

섹션

설명

SHN_UNDEF

0

미정의

SHN_ABS

0xfff1

절대값을 갖는 심볼

SHN_COMMON

0xfff2

일반적인 심볼

   

/bin/ls의 예를 보자. 0번째 심볼 정보는 다음과 같이 비어 있다.

▷ /bin/ls의 위의 예에서 맨 마지막에 있는 심볼 정보를 알아 보자.

주소 0006104 부터 시작하는 이 바이트 열은 다음과 같은 의미를 갖는다.

( ※ 아키텍처(x86) 리틀 엔디안 이기 때문에 반대로 읽는다)

st_name = 0x2db

st_value = 0x08061320

st_size = 0x4

st_intfo = 0x11 = (STB_GLOBAL | STT_OBJECT)

st_other = 0

st_shndx = 0x1a = 26

   

▷ 여기서 심볼명을 가리키는 오프셋 값 st_name은 '0x2db'로 되어 있다.

.dynstr 섹션을 보면 스트링 테이블은 시작 위치로부터 '0xc54' 바이트 떨어진 위치에서 시작한다.

- 여기서 '0xc54'에 0x2db(st_name)를 더한 값인 '0xf2f' 위치에서 시작하는 문자열을 보면,

' optarg'라는 문자열을 찾을 수 있다.

- 이와 같이 st_name = '0x2db'에 해당하는 심볼은 'optarg'임을 알 수 있다.

   

▷ 이는 readelf -s 출력 결과에서 위의 심볼 정보를 확인 할 수 있다.

  

   

   

:: [ 재배치 정보 ]

▷ SHT_RELA 또는 SHT_REL 타입의 섹션은 재배치 정보를 갖는다.

▷ SHT_RELA는 다음과 같은 Rela 구조의 테이블을 갖는다.

   

▷ SHT_REL의 경우는 다음과 같이 r_addend가 없는 Rel 구조의 테이블을 갖는다.

   

▷ r_offset은 재배치를 해야 하는 위치를 가리키는, 섹션 첫 위치로부터의 오프셋이다.

▷ r_info는 재배치 타입 또는 심볼테이블의 인덱스 등의 정보를 포함한다.

▷ Rela의 경우 재배치할 경우에 항상 더해야 할 값을 r_addend로 갖고 있다.

  

Posted by devanix