2011. 7. 3. 23:11

♧ c++ 컴파일러는 단 하나의 이름을 갖는 심볼이 되도록

name mangling이라는 과정을 수행한다.

여기서는 명령행에서 C++의 심볼을 demangle하는 방법을 소개한다.

   

▷ C++ 오브젝트 파일을 nm명령으로 확인하면 기본적으로 심볼명은

name mangling된 읽기 어려운 형식으로 출력 된다.

   

c++filt를 이용해여 demangle.

   

nm에 --demangle 옵션을 사용하여 demangle.

  

Posted by devanix
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. 20:56

nm

   

♧ 오브젝트 파일을 대상으로 파일에 포함된 심볼의 리스트를 볼 수 있다.

   

[ nm 사용법 ]

▶ [ 일반적인 nm 명령 실행 ]

▷ 오브젝트 파일에 포함되어 있는 심볼을 알파벳순으로 1행씩 출력.

▷ 기본적인 출력 형식은 bsd 형식::

[ 심볼 값 ] [ 심볼 클래스 ] [ 심볼명이 출력 ]

▷ 심볼 값이 정해지지 않은 심볼(심볼 클래스 U)은 심볼 값이 출력되지 않고 빈 컬럼으로 표시.

▷ nm에 인수를 생략한 경우는 현재 디렉토리의 a.out을 대상으로 실행.

   

▶ [ 출력 순서를 역순으로 출력 ] -r옵션 (--reverse-sort 옵션)

   

▶ [ 심볼 크기를 작은 순으로 정렬 ] --size-sort 옵션

▷ -r옵션을 동시에 사용하면 크기가 큰 순으로 정렬

미정의 심볼은 출력되지 않음.

▷ 기본적인 출력 형식에 --size-sort옵션을 지정하면 첫 컬럼에 심볼 크기가 출력되며,

심볼 값은 생략된다.

   

▶ [ 심볼 크기를 큰 순으로 심볼 값과 함께 출력 ] -S옵션 (심볼 값 출력)

▷ 이때 각 행의 출력 형식 :: [ 심볼 값 ] [ 심볼 크기 ] [ 심볼 클래스 ] [ 심볼명 ]

   

▶ [ 출력 형식 지정 ] ( -f format)

# sysv 형식

# posix 형식

# 디폴트는 bsd 형식

   

▶ [ 오브젝트 파일을 복수 시정한 경우 ]

▷ 오브젝트 파일을 복수 지정한 경우에는 각 오브젝트 파일의 심볼을 정렬해서 출력.

   

▶ [ 심볼에 속한 오브젝트 파일명을 함께 출력 ] -A (또는 -o | --print-file-name)

▷ 각 행 처음에 해당 심볼이 속한 오브젝트 파일명을 출력.

   

▶ [ strip(1)을 사용해 실행 파일의 심볼을 지울 경우 ]

▷ 단, 정적 바이너리가 아닌 경우에는 공유 라이브러리를

동적으로 링크하기 위한 심볼 정보는 남아 있다.

▶ 이를 확인하려면 -D옵션(--dynamic 옵션)을 사용.

   

▶ [ 정적 라이브러리를 대상으로 출력 ]

% nm /usr/lib/libc.a

▷ 라이브러리 파일에 포함되어 있는 오브젝트 파일별로 심볼이 출력.

  

   

   

[ 심볼 클래스 ]

♧ nm의 출력 결과를 해석하려면 심볼 클래스의 의미를 이해해야 한다.
심볼에는 몇개의 클래스가 있으며 nm에서는 이를 한 문자로 표현한다.
 

▷ 대소문자를 구별 ::

대문자 = [전역 심볼(외부 참조됨)] / 소문자 = [지역 심볼]

   

▶ [ 알파벳순으로 나열 ]

심볼 클래스

설명

A

심볼 값이 절대값으로 링크해도 변하지 않음.

B

초기화되지 않은 데이터 영역(BSS)에 존재함.

C

공유(common) 심볼. 초기화 되지 않은 데이터

D

초기화된 데이터 섹션에 존재

G

작은 오브젝트에 사용되며 초기화된 데이터 섹션에 존재

(부근의 심볼이 보다 효율적으로 접근할 수 잇는 경우가 있으므로)

I

다른 심볼의 간접참조. a.out의 GNU 확장

N

디버그용 심볼

R

읽기전용 데이터 섹션에 존재

S

작은 오브젝트에 사용되며 초기화되지 않은 데이터 섹션에 존재

T

텍스트 섹션에 존재

U

정의되지 않은 심볼. 다른 오브젝트 파일 또는 공유 라이브러리에 실제

V

심볼이 존재

W

weak 오브젝트

a.out 오브젝트 파일 내의 stabs 심볼(디버그 정보 등)

?

알 수 없는 클래스

▶ [ 섹션별로 분류 ]

섹션

심볼 클래스

참조가능범위

텍스트 섹션

T

전역

  

t

지역

데이터 섹션

D

전역

  

G

전역(작은 오브젝트용)

  

d

지역

  

g

지역(작은 오브젝트용)

읽기전용 데이터

R

전역

  

r

지역

BSS(초기화되지 않은 데이터)

B

전역

  

S

전역(작은 오브젝트용)

  

b

지역

  

s

지역(작은 오브젝트용)

weak 오브젝트

V

전역

  

v

지역

weak 심볼

W

전역

  

w

지역

공유(common)

C

전역

디버그용

N

전역

  

n

지역

  

stabs

절대값

A

전역

  

a

지역

미정의

U

전역

간접참조

I

전역

  

i

지역

알 수 없는 클래스

?

  

 

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