BLOG ARTICLE 유니버셜 바이너리 | 1 ARTICLE FOUND

  1. 2008.01.18 OS X 실행파일(Mach-O) 헤더 구조와 유용한 툴들 (4)

MS윈도우에서 사용하는 PE와 유닉스의 ELF 포맷은 유명하지만 맥에서 사용하는 Mach-O 포맷은 상대적으로 덜 알려져 있는 것 같습니다. 그래서 OS X의 실행파일 포맷 중 헤더 부분에 대해서만 간단히 알아 보도록 하겠습니다.

실행 파일 헤더 구조라는 다소 무거운 제목과는 달리 간단히 구조만 알아 보고 몇가지 편리한 툴들을 살펴 볼려고 합니다. 상세한 자료가 필요하신 분들은 ADC에서 아래의 문서를 참조하시면 될 것 같습니다.

1. Mach-O & fat-binary

사용자 삽입 이미지
Mach 커널을 사용하는 OS X는 Mach-O란 실행파일 포맷을 사용합니다. Mach-O 파일 포맷은 좌측과 같이 되어 있습니다. (이미지는 ADC에서 가지고 왔습니다.)

Mach-O 파일은 헤더와 load commands, 세그먼트들로 구성된 데이터로 이루어져 있습니다.

load commands는 OS가 어플리케이션 실행시에 라이브러리를 올리는 등의 실행에 필요한 명령어들의 집합입니다.

헤더에 대한 상세한 내용은 툴 사용법과 함께 알아 보겠습니다.


fat-binary (멀티아키텍쳐 바이너리)
위의 구조는 한 아키텍쳐를 위한 단일 구조이며, OS X에서는 여러 아키텍쳐를 지원하는 유니버셜 바이너리를 위하여 fat이란 구조를 사용하고 있습니다.

사용자 삽입 이미지
fat은 하나의 실행파일이 여러 아키텍쳐에 적용될 수 있게 하기 위한 포맷입니다. 이는 OS X에서 PPC(PowerPC)와 x86 코드를 사용할 수 있게 하여줍니다. 우리가 흔히 유니버셜 바이너리(UB)라고 부르는 실행파일 포맷입니다.

좌측을 보면 위의 구조(thin)와는 달리 fat에 관련된 헤더들이 추가되었습니다. 각각의 Fat Architecture는 각각의 실행 코드를 가르키고 있으며, OS에서 로드시에 해당 아키텍쳐에 맞는 코드 블록(Mach-O 포멧)이 로딩됩니다.     




2. lipo
lipo는 OS X에서 유니버셜 바이너리를 관리하기 위한 툴입니다. lipo를 이용해서 유니버셜 실행 파일로 부터 한 아키텍쳐를 지원하는 실행파일을 추출을 할 수 있습니다.

사용자 삽입 이미지
웹브라우져인 오페라 실행파일로 테스트를 해보겠습니다. 실행파일은 해당 어플리케이션 디렉토리 (Opera.app)에서 Contents/MacOS/ 내에 있습니다.

터미널에서
> lipo -detailed_info ./Opera
를 실행하시면 좌측과 같은 fat과 지원하는 각 아키텍쳐들의 세부 정보를 확인할 수 있습니다.


오페라는 유니버셜 바이너리(fat)로 되어 있으며 위와 같이 nfat_arch(fat_arch 구조체 갯수)가 2이이므로 두개의 네이티브 코드를 가지고 있습니다. 그 아래의 메지시로 PPC와 i386(x86)을 지원한다는 것을 알 수 있습니다. 각각의 내용은 fat_arch 구조체의 내용입니다. fat_arch 구조체는 아래에서 확인해 보겠습니다.

이제 lipo의 "-thin" 옵션을 이용해서 PPC만 지원하는 실행파일을 추출하여 보겠습니다. 터미널에서 아래와 같이 lipo 명령을 실행합니다.

사용자 삽입 이미지

실행이 완료되면 위와 같이 Opera2란 파일로 ppc 네이티브 실행파일이 만들어져 있습니다. 파일 크기를 확인하시면 fat 헤더와 x86 코드가 빠져있기 때문에 거의 반으로 줄어 있습니다. 새로 생성한 PPC 파일을 Opera로 이름을 변경하고 GUI에서 확인해 보겠습니다.

사용자 삽입 이미지

좌측이 유니버셜 버젼이 적용된 원래 모습이며, 우측이 lipo를 이용하여 새로 만든 PPC로 적용시킨 후 확인해 본 모습니다. Universal에서 PowerPC로 변경되어 있습니다.

3. otool
otool은 실행파일(or obj, lib 등)의 정보를 보여주는 OS X에 내장된 툴입니다. OS X는 기존의 유닉스 계열과는 달리 "ldd"라는 공유라이브러리의 의존성을 검사하는 툴이 없습니다. otool은 ldd의 기능도 포함하고 있으니 OS X에서는 이를 사용하면 됩니다.

터미널에서 "otool"만 입력하시면 아래와 같이 간단한 사용방법을 보실 수 있습니다.

사용자 삽입 이미지

> otool -L ./Opera 로 오페라에서 사용하는 공유라이브러리 정보를 출력합니다. 위와 같이 오페라는 "카본 프레임워크"를 사용하고 있음을 알 수 있습니다.  또한 위의 lipo 보다 더 실행 파일의 다양한 헤더들과, load command등의 정보를 확인할 수 있습니다. (lipo는 유니버셜 바이너리 파일만 적용할 수 있습니다.)

4. OxED
사용자 삽입 이미지
0xED는 맥에서 바이너리 파일을 볼 수 있는 핵사 에디터입니다. 일반 텍스트 에디터에서 얻을 수 없는 바이너리 파일들의 정보를 얻을 수 있습니다.



아래는 0xED를 이용하여 오페라의 실행파일(유니버셜)을 열어 본 모습입니다.

사용자 삽입 이미지

헤더의 시작은 fat_heaer와 fat_arch로 이루어져 있습니다. 우선 /usr/include/mach-o/fat.h에서 이 구조체들을 확인해 보겠습니다.

#define FAT_MAGIC   0xcafebabe
#define FAT_CIGAM   0xbebafeca  /* NXSwapLong(FAT_MAGIC) */

struct fat_header {
    uint32_t    magic;      /* FAT_MAGIC */
    uint32_t    nfat_arch;  /* number of structs that follow */
};

struct fat_arch {
    cpu_type_t  cputype;    /* cpu specifier (int) */
    cpu_subtype_t   cpusubtype; /* machine specifier (int) */
    uint32_t    offset;     /* file offset to this object file */
    uint32_t    size;       /* size of this object file */
    uint32_t    align;      /* alignment as a power of 2 */
};

fat_header
첫번째 4바이트의 "CA FE BA BE"를 보시면 fat_headerd의 magic 값이며 소스에서 FAT_MAGIC 정의된 값입니다. 이는 실행파일이 fat 구조이며 유너버셜 바이너리라는 것을 알려 줍니다.

두번째 nfat_arch는 이 실행파일이 지원하는 아키텍쳐의 갯수이며 그 아래에 위치할 fat_arch 구조체의 갯수와도 동일합니다. 4바이트 int형으로 [00 00 00 02]로 2개의 아키텍쳐를 지원합니다.

fat_arch
지원하는 아키텍쳐의 정보를 담고 있는 구조체 입니다. 첫번째는 cpu의 종류를 나타내는 cputype입니다. 16진수 [00 00 00 12]로 10진수 18의 값을 가지고 있습니다. 두번째 fat_arch는 이 값이 [00 00 00 07]로  7의 값을 가지고 있습니다.

#define CPU_TYPE_POWERPC        ((cpu_type_t) 18)

#define CPU_TYPE_X86        ((cpu_type_t) 7)
#define CPU_TYPE_I386       CPU_TYPE_X86        /* compatibility */

이 값들은 /usr/include/mach/machine.h 파일에 아래와 같이 정의 되어 있습니다. 각각 PPC(18)와 x86(7) 아키텍쳐를 지원하고 있음을 알 수 있습니다.

다음은 lipo로 만든 PPC 실행파일을 열어 본 모습니다.
사용자 삽입 이미지

fat_ 과 관련된 구조체 없이 바로 mach_header가 위치합니다. 아래는 mach_header가 정의된 /usr/include/mach-o/loader.h 헤더파일의 일부입니다.

/*
 * The 32-bit mach header appears at the very beginning of the object file for
 * 32-bit architectures.
 */
struct mach_header {
    uint32_t    magic;      /* mach magic number identifier */
    cpu_type_t  cputype;    /* cpu specifier */
    cpu_subtype_t   cpusubtype; /* machine specifier */
    uint32_t    filetype;   /* type of file */
    uint32_t    ncmds;      /* number of load commands */
    uint32_t    sizeofcmds; /* the size of all the load commands */
    uint32_t    flags;      /* flags */
};

/* Constant for the magic field of the mach_header (32-bit architectures) */
#define MH_MAGIC    0xfeedface  /* the mach magic number */
#define MH_CIGAM    0xcefaedfe  /* NXSwapInt(MH_MAGIC) */

각각의 값들은 아래와 같습니다.

magic [FE ED FA CE]
Mach-O 포맷의 파일을 알려 주는 필드입니다. 이는 빅 엔디언을 사용하는 PPC 파일의 값이고 리틀 엔디언을 사용하는 x86을 지원하는 코드에서는 "CE FA ED FE"를 가지고 있습니다.

cputype [00 00 00 12]
12로 위와 동일하게 PPC를 지워합니다.

cpusubtype [00 00 00 00]
PPC subtype의 0은 CPU_SUBTYPE_POWERPC_ALL로 정의되어 있습니다.

filetype [00 00 00 02]
파일타입 2는 MH_EXECUTE로 정의 되어 있으며 실행파일을 의미합니다.

ncmsds [00 00 00 0B]
11개의 load command를 가지고 있음을 의미합니다.

sizeofcmds [00 00 08 CC]
load command의 크기로 2252byte입니다.

flags [00 00 00 85]
bit로 정의된 상세 정보입니다. 아래와 같은 세가지 bit가 세팅되어 있습니다.

#define MH_NOUNDEFS 0x1     /* the object file has no undefined
                       references */
#define MH_DYLDLINK 0x4     /* the object file is input for the
                       dynamic linker and can't be staticly
                       link edited again */
#define MH_TWOLEVEL 0x80        /* the image is using two-level name
                      space bindings */

cputype과 cpusubtype의 값에 대해서는 /usr/include/mach/machine.h 헤더파일을 그 외 정보들은  /usr/include/mach-o/loader.h에서 자세한 정보를 확인하실 수 있습니다.

딱 저의 궁금증 까지만 알아 보았습니다. 더 자료가 필요하신 분들은 위에 링크된 ADC 레퍼런스와 또 다른 문서인 Universal Binary Programming Guidelines가 도움이 되실 것입니다.