2009년 10월 23일 금요일

Shell과 Makefile에서 PATH수정하기

PATH 앞 혹은 뒤쪽에 다른 PATH를 추가하고자 할때 아래와 같이 하면 된다.

Shell에서는...


export PATH=/added_path:$PATH


Makefile에서는...

TEMP=/added_path:$(PATH)
PATH:=$(TEMP)# :=를 사용해서 $(PATH)가 recusive하게 해석되지 않도록 함.
PATH=$(TEMP) # 이렇게 하면 error, $(PATH)가 recusive하게 해석됨.

혹은
PATH=/added_path:$(PATH) #이렇게 해도 상관 없음
PATH:=/added_path:$(PATH)
Makefile에서 매크로를 참조할때는 꼭 괄호를 사용할 것.
Shell에서는 괄호가 없어도 됨.

2009년 10월 2일 금요일

State Patten을 C로 짜려면?

C에서는 상속을 사용하지 못하지만, 그렇다고 State Patten을 구현하지 못하는 것은 아니다. 아래와 같은 구현으로 C에서 State Patten의 장점을 사용할 수 있을 것 같다. C에서는 인터페이스나 상속이 지원되지 않으므로 function table을 사용하여 이벤트 발생시 각 상태별 실행 함수를 지정하도록 하였다. 사실상 이 function table이 구현의 핵심으로, 상태 변경을 발생시키는 이벤트가 추가 할 때마다, 모든 function table의 해당 함수에 대한 함수 포인터 혹은 null function pointer를 추가해야 한다. 바로 이 부분이 상속과 가상 함수를 지원하지 못하는 C에서 State Pattern을 구현할 때 발생하는 불편함이기는 하지만 여전히 State Pattern의 장점을 그대로 누릴 수 있다. 또한 (당연히) 동일 방법을 사용하여 Strategy Pattern 역시 구현 가능하다.

아래 예에서는 구현을 단순화하기 위해서 상태는 ON/OFF두가지 상태만을 가정하였고, 'n' 입력시 ON상태로, 'f' 입력시 OFF상태로 상태 천이를 하도록 하였다.



#include <stdio.h>

/**********************************************************************
*
* 1. Type defination for Power State management
*
**********************************************************************/

typedef enum {ON,OFF} PowerState;

typedef struct
{
  PowerState currPS;
  void (*on_received_fptr)(struct PwrState **ps );
  void (*off_received_fptr)(struct PwrState **ps );
}PwrState;

/**********************************************************************
*
* 2. Function table for each State.
*
**********************************************************************/
//Prefix ON/OFF means current status when these functions are called.
void ON_off_received(PwrState **ps );
void OFF_on_received(PwrState **ps );
void null_func(PwrState **ps);

//C에서는 가상 함수를 사용할 수 없기 때문에, 아래와 같은 각 상태별
//함수 테이블을 별도로 유지해야 한다.

PwrState psON= {ON,   null_func,      ON_off_received};
PwrState psOFF={OFF,  OFF_on_received,  null_func};


void ON_off_received(PwrState **ps)
{
  printf("Currnt State =%d : 0:ON 1:OFF \n",(int)(*ps)->currPS);
  printf("%s is called\n",__func__);

  printf("change curr power state from ON to OFF\n");
  *ps=&psOFF;
}

void OFF_on_received(PwrState **ps)
{
  printf("Currnt State =%d : 0:ON 1:OFF \n",(int)(*ps)->currPS);
  printf("%s is called\n",__func__);

  printf("change curr power state from OFF to ON\n");
  *ps=&psON;
}

void null_func(PwrState **ps){}

int main(void)
{
  char keyin;
  PwrState *ps=&psOFF;

  printf("Current power state is OFF\n");
  printf("type \'n\' to change power state to ON\n");
  printf("type \'f\' to change power state to OFF\n");

  while(1)
  {
    keyin = getchar();

    switch(keyin)
    {
      case 'n':// change state to ON
        ps->on_received_fptr(&ps);
        break;
      case 'f':// change state to OFF
        ps->off_received_fptr(&ps);
        break;
      default:
        break;
    }
  }
}

2009년 10월 1일 목요일

원하는 폴더에서 마우스 오른쪽 버튼으로 커맨드 창 열기

아래 내용을 filename.reg기울임꼴로 저장한 후 실행하면 레지스트리에 해당 내용이 추가되며, 추가된 이후 실행된 탐색기에서는 특정 폴더에서 마우스 오른쪽 버튼 클릭시 뜨는 팝업 메뉴에 "Dos Prompt(Z)"가 추가된다. &Z는 팝업메뉴에서의 단축키를 Z로 지정한 것이며, "Dos Prompt" 대신 원하는 문자열을 사용할 수 있다.

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\Directory\shell\OpenNew]@="Dos Prompr t(&Z)"
[HKEY_CLASSES_ROOT\Directory\shell\OpenNew\Command]@="cmd.exe /k"


아래 블로그에서 관련 내용을 포함한 추가 정보를 얻을 수 있습니다.
http://youngsam.kr/

2009년 7월 3일 금요일

ELF Header

ELF Header 혹은 ELF File Header는 보통 52바이트로 아래와 같이 내용을 살펴 볼 수 있다.

--------------------------------------------------------
Dukeru>readelf -h testApp
ELF Header:
Magic: 7f 45 4c 46 01 02 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, big endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: PowerPC
Version: 0x1
Entry point address: 0x48041110
Start of program headers: 52 (bytes into file)
Start of section headers: 275611 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 6
Size of section headers: 40 (bytes)
Number of section headers: 29
Section header string table index: 28
--------------------------------------------------------

0x0000-0x0003 (EI_MAG) Magic
Magic: 7f 45 4c 46 01 02 01 00 00 00 00 00 00 00 00 00
이 파일이 ELF 파일임을 나태냄. 45 4C 46이 ASCII로 E L F임.

0x0004 (EI_CLASS) Class
Magic: 7f 45 4c 46 01 02 01 00 00 00 00 00 00 00 00 00
Class: ELF32
32bit 환경에서 실행되는 ELF파일임을 뜻함.
01: 32bit
02: 64bit


0x0005 (EI_DATA Encoding)
Magic: 7f 45 4c 46 01 02 01 00 00 00 00 00 00 00 00 00
Data: 2's complement, big endian
이 파일에 데이터가 Big Endian 포맷임.
01: Little Endian
02: Big Endian

0x0006 (EI_VERSION)
Magic: 7f 45 4c 46 01 02 01 00 00 00 00 00 00 00 00 00
Version: 1 (current)
ELF Header Version을 뜻함. EV_CURRENT가 값으로 사용되며 일반적으로 EV_CURRENT는 1으로 정의되어 있음.

0x0007 - 0x000F (EI-PAD)
Magic: 7f 45 4c 46 01 02 01 00 00 00 00 00 00 00 00 00
OS/ABI: UNIX - System V :ABI형식
ABI Version: 0
몇몇 가지 부가 정보가 있으나 일반적으로 모두 0임.

0x0010-0x0011 (E-TYPE)
0000010 00 02 00 14 00 00 00 01 48 04 11 10 00 00 00 34
Type: EXEC (Executable file)
1: Relocatable file
2: Executable file
3: Shared object file
4: Core file

0x0012-0x0013 (E-MACHINE)
0000010 00 02 00 14 00 00 00 01 48 04 11 10 00 00 00 34
Machine: PowerPC
0x03: Intel386
0x14: PowerPC

0x0014-0x0017 (E_VERSION)
0000010 00 02 00 14 00 00 00 01 48 04 11 10 00 00 00 34
Version: 0x1
Object file version
0:Invalid
1:Current version

0x0018-0x001B (E_ENTRY)
0000010 00 02 00 14 00 00 00 01 48 04 11 10 00 00 00 34
Entry point address: 0x48041110
Virtual Address Starting Process, 프로그램의 시작 주소, main이 아니라 _start 함수의 주소임. main은 _start에서 호출된다.

0x001C-0x001F (E-PHOFF) Program Header Offset
0000010 00 02 00 14 00 00 00 01 48 04 11 10 00 00 00 34
Start of program headers: 52 (bytes into file)
파일 시작위치에서 Program Header Table까지의 offset.

0x0020-0x0023 (E-SHOFF) Section Header Offset
0000020 00 04 34 9b 00 00 00 00 00 34 00 20 00 06 00 28
Start of section headers: 275611 (bytes into file)
파일 시작위치에서 Section Header Table까지의 offset.

0x0024-0x0027 (E_FLAGS)
0000020 00 04 34 9b 00 00 00 00 00 34 00 20 00 06 00 28
Flags: 0x0
Process Specific Flags.

0x0028-0x0029 (E_EHSIZE) ELF Header Size.
0000020 00 04 34 9b 00 00 00 00 00 34 00 20 00 06 00 28
Size of this header: 52 (bytes)
ELF header의 크기이면서 Program Header의 시작 위치

0x002A-0x002B (E-PHENTSIZE) Program Header Entry Size
0000020 00 04 34 9b 00 00 00 00 00 34 00 20 00 06 00 28
Size of program headers: 32 (bytes)
Program Header Table의 각 item의 크기. Program Header는 각 Segment당 한개씩 있음.

0x002C-0x002D (E_PHNUM) Program Header Number
0000020 00 04 34 9b 00 00 00 00 00 34 00 20 00 06 00 28
Number of program headers: 6
6개의 Segment, 즉 6개의 Program Header가 있다는 뜻.

0x002E-0x002F (E_SHENTSIZE) Section Header Entry Size
0000020 00 04 34 9b 00 00 00 00 00 34 00 20 00 06 00 28
Size of section headers: 40 (bytes)
Section Header Table의 각 item의 크기.

0x0030-0x0031 (E_SHNUM) Section Header Number
0000030 00 1d 00 1c 00 00 00 06 00 00 00 34 48 04 00 34
Number of section headers: 29
29개의 section header가 있다는 뜻.

0x0032-0x0033 (E_SHSTRNDX) Section Header String Index
0000030 00 1d 00 1c 00 00 00 06 00 00 00 34 48 04 00 34
Section header string table index: 28
Section Header table에서 String section의 header가 있는 index.
E-SHOFF + (E_SHSTRNDX * E_SHENTSIZE) 가 파일에서 String Section의 header의 위치가 된다.

해당 섹션의 이름을 얻는 방법:
원하는 Section Header의 sh_name은 section name이 string section의 시작으로부터 몇바이트 위치에 있는지 offset값을 가지고 있다.
따라서 section header table을 참조해서 string section의 시작주소를 구하고 여기에 sh_name을 더하면 된다. (section header에 대해서는 다음에 자세히 설명한다.)

string section의 시작주소는 section header table에서 구할 수 있다.

따라서 string section header에서 sh_offset을 읽어 원하는 section header의 sh_name을 더하면, 원하는 section의 이름을 얻을 수 있다.

Addr. of a section name = "sh_name" of the section + "sh_offset" of string section

ELF? 엘프?

ELF파일은 유닉스 계열 OS에서 가장 많이 사용되는 실행파일 형식으로 아래와 같은 파일을들이 이 형식을 사용한다.
  • Object file.
  • Executable file.
  • Shared Library.
  • core파일.
.a 확장자를 가지는 일반 Library파일은 단순한 archive파일로 ELF형식이 아니다.

아래 ELF파일의 구조에서 살펴보았듯이 ELF파일은 ELF file header, program header table, sections 그리고 section header table로 구성되어 있다.


ELF file구조 및 readelf 사용법.

ELF file구조
  • ELF file Header
  • Program Header Table
  • Section1
  • Section2
  • ...
  • Section Header Table
- Program Header Table은 오브젝트 파일에서는 optional이다. Program Header는 세그먼트에 대한 정보를 가지고 있는데, 오브젝트 파일은 이러한 정보가 없으므로 Program Header가 필요하지 않다.
- Section Header는 실행파일에서는 optional이다. Section header가 없거나 손상되어도 프로그램의 실행에는 지장이 없으나 이런 파일은 디버거를 사용하여 디버깅을 할 수 없다.

--------------------------------
ELF header
--------------------------------
Program Header 1
-----------------------
Program Header 2
-----------------------
Program Header 3
-----------------------
...
-----------------------
Program Header N
--------------------------------

sections...

--------------------------------
Section Header 1
-----------------------
Section Header 2
-----------------------
...
-----------------------
Section Header M
----------------------------------

readelf사용법
    Usage: readelf elf-file(s)
      Display information about the contents of ELF format files
        Options are:
        • -h Display the ELF file header
        • -l Display the program headers
        • -S Display the sections' header
        • -d Display the dynamic segment (if present)
          부연설명:
          ELF는 unix계열에서 현재 가장 널리 사용되는 실행 파일 포맷이다. a.out과 COFF는 이제 많이 사용되지 않는다. 단, MS Windows계열에서 사용하는 실행 파일 포맷인 PE(Potable Executable) 포맷이 COFF를 약간 수정한것이라고 한다.

          2009년 6월 7일 일요일

          디버깅은 근성이다!!!

          개발을 하다보면 문제의 원인을 찾지 못해 문제를 바로 해결하지 못하고 짧게는 며칠에서 길게는 몇 달 씩 고생하는 경우가 생기곤 한다. 최근 1년동안에는 두 번 정도 이런 문제에 발목을 잡혀 여러날을 맘고생하며 보내야 했다. 먼저 부딪친 문제는 플래시 메모리용 파일 시스템이 망가지는 문제였고, 이 문제가 해결되고 몇 달이 지나지 않아 메인 어플리케이션이 SIGSEV로 terminated 되는 문제가 발생하였는데, 프로그램이 죽는 위치가 직접 작성한 코드가 아니라 Rose RT가 제공하는 코드고, 직접 재현이 불가능하고 바다건너 미국에서만 발생한다고 하여 도무지 디버깅을 하기 쉽지 않은 문제였다. 두 문제 모두 몇 달에 걸친 디버깅 과정끝에 다행이 원인을 찾을 수 있었지만, 문제를 찾은 후 좀 더 빨리 문제해결하지 못한것에 대한 아쉬움이 남는다.

          문제를 해결하기위한 첫번째 과정은 문제를 재현하는 것이다. 문제 재현, 이것이 문제 해결에 가장 중요한 항목이지만, 실제 개발중에는 여러가지 이유로 문제 재현이 용이하지 않는 경우가 발생하곤 한다. 따라서 재현이 잘 되지 않는 문제가 발생하면, 해결하는데 오랜 시간이 걸리는 골치거리로 자라날 가능성이 크다. 따라서 문제 재현이 어려울 경우에는 문제를 재현하는 방법을 찾는 것이 문제를 해결하는 것이 된다.

          앞에서 언급한 두 문제 모두, 역시 해결의 열쇠는 "문제 재현"이었다. 첫번째 문제는 수만대의 제품이 시장에 팔려나간 상태에서 한달에 한번꼴로 문제 발생이 보고 되었지만 개발실에서는 좀처럼 문제를 재현시킬 수 없었고, 두번째 문제는 테스트 벤치에서는 발생하지 않고, 멀리 미국에 서만 재현되는 문제였다. 결국 두 문제 모두 결국 문제를 재현할 수 있는 환경이 갖추어진 후에야 해결되었는데, 이 과정에서 적잖은 근성을 소모해야 했다.

          운동 선수만 근성이 필요한게 아니다. 개발자들에게도 직장인에게도 근성이 필요하며, 적절한 실력을 인정받기 위해서 꼭 필요한 요소중의 하나로 감히 근성이 한자리를 차지할 것 같다. 사람들이 어려운 문제에 이르렀을때 이를 해결해 나가는 저마다의 노하우가 있지만, 때론 자신의 지식으로 바로 해결할 수 없는 문제에 부딪치게 된다. 이때 문제를 해결하는 열쇠는 다름아닌 "근성"이다. 근성이 없는 사람은 여러가지 핑게를 대며 문제를 회피하거나 외면하는 수순에 들어가게 되는데, 이때 그들은 나약함과 비굴함을 얼핏 밖으로 비치기도 한다.

          사람을 평가할 때 사용하는 중요한 지표로 attitude, 즉 태도를 사용하는데, 근성이 여기에 포함될 것이다. 즉 실력있는 사람으로 인정받기 위해서는 근성이 중요하다는 말이다. 박지성이 높이 평가받는 이유도 "근성"이 아닐까? 물론 근성만있고 문제 해결을 못한다면 문제가 있지만, 근성이 있는 사람이 문제 해결을 위한 지식을 쌓아왔을 가능성이 더 높지 않을까 ?

          앞에서 말한 골치거리 문제들을 해결할 때도 "근성"이 나의 친구가 되어 주었다. 아래에 나와 나의 친구 "근성"이 어떻게 문제를 해결했는지 정리하고자 한다.

          첫번째 문제는 수만대의 제품이 풀린 시장에서 한달에 한번정도만 보고되는 재현 빈도가 낮은 문제였다. 하지만 문제가 꾸준히 발생하고 있었고, file system이 corrupt되면 그 이 후 정상적으로 시스템을 사용할 수 없었기 때문에 꽤나 치명적인 문제였다.

          우리는 당연히 file system이 깨지는 것은 flash device driver나 file system library에 문제가 있는 것이라고 생각했지만, OS업체는 해당 driver와 library가 여러 업체에서 사용하지만 문제가 거의 보고된 적이 없기 때문에 s/w문제 보다는 flash device의 불량일 것이라고 추정하며 책임을 회피하고 있었다.

          당연한 수순으로, 문제 재현을 위해 우리 프로그램의 파일 접근 과정을 시뮬레이션하는 작은 테스트 프로그램을 만들어 며칠씩 반복해서 돌려보았지만 문제를 재현시킬 수 없었다.

          결국 문제를 해결하기 위해서 캐나다에 있는 OS개발 업체로 우리측 개발자 3명이 파견되었고, OS개발 업체 개발자 2명 및 여러명의 테스터가 할당되었다. 우리 개발자들이 우리쪽 s/w의 file access 하는 디자인을 OS쪽 개발자들에게 설명하여 주고, 반대로 OS쪽 개발자들도 flash device driver와 file system 구현 코드를 우리에게 설명하여 주었다. 재미나게도 우리는 서로 상대방의 프로그램에서 문제를 찾아내는데 집중하였다. (자신이 더 잘 알고 있는 자신의 코드에서 문제를 찾아내는게 더 쉬울것임에도 불구하고...)

          상호 프로그램에 대한 디자인 리뷰를 통해서 서로의 시스템을 대강 이해하게 되었다. 하지만 그 정도로 복잡한 s/w내에 잠자고 있는 문제를 찾을 수는 없었다. 이러면서 몇달이 흘러갔고 결국은 디버깅용 출력 코드를 삽입한 바이너리를 여러개의 제품에 넣어 expose 테스트를 하며 문제가 재현되기를 기다리기로 하였다. 그러나 문제가 몇 대의 테스트 제품에서 재현되기에는 너무 발생 빈도가 낮았고, 문제가 재현되어도 디버깅용 출력 코드가 충분한 정보를 제공한다는 보장은 어디에도 없었다.

          해결의 실마리는 의외로 엉뚱한 곳에서 나왔다. 해당 제품을 함께 개발한 다른 개발자들과 파일 접근 관련 경험을 문답하던 중 마지막 데이터 몇바이트를 유실하는 경험을 한 프로그래머가 있었는데, 마침 유실되는데이터가 꼭 필요한 데이터가 아니었기에, 아예 그 데이터를 저장하지 않는 방법으로 문제를 피해갔다는 것이었다. 그리고나서 나중에 자신이 알아낸 것은 fclose()를 하지 않고 프로그램을 종료시키면 문제가 발생하는 것 같다는 정보도 함께 주었다.

          확인 결과 fclose()를 호출하지 않으면 프로그램 종료시 마지막에 저장한 몇바이트 정도가 유실 되었고 이는fclose를 호출하지 않으면 flash write cache의 내용이 file system에 commit되지 않는 것으로 추정되었다. 하지만 이 경우 유저 데이터만 유실될 뿐 file system에는 문제가 없었다.

          하지만, 뭔가 이런 오동작과 이번 문제가 관련이 있을 것만 같아 이전에 작성한 시뮬레이션용 프로그램을 fclose를 하지 않도록 수정하여 프로그램을 돌렸고, 채 1분이 지나지 않아서 거짓말 처럼 해당 문제가 재현되었다.

          문제를 재현하는 테스트 프로그램을 OS업체에 보내자 OS업체는 하루만에 문제를 해결한 flash filesystem driver를 보내왔다. 문제는다소 복잡한 조건하에서만 발생하였는데, fclose()를 빼먹는 것이 필수 조건이었다. OS는 프로그램 종료시 어플리케이션이 사용한 모든 자원을 안전하게 반납하여야 하기 때문에 fclose() 하지 않아도 이를 안전하게 처리해 주어야 하지만, flash file system의 버그로 문제가 발생한 것이었다.

          해당 문제는 거의 몇개월이나 걸려서 해결이 되었고, 문제 해결에 대한 성취감도 대단하였다. 문제를 해결하기 위해서 여러가지 과거의 경험이 총 동원되었지만, 모두가 문제 해결에 지쳐가고 회의적으로 생각하는 시점에서 계속 문제 재현을 시도했던것은 나의 친구 "근성"이었던 것 같다.

          더 빨리 문제를 해결하지 못한것에 대한 아쉬움이 어쩔수 없이 내 뒤에 붙어있다. 하지만 아이러니한 것은, 문제가 오래끈 만큼 해결에 대한 주위의 인정이 커졌다는 것이다. 문제를 손쉽게 해결하였다면 누가 알아주었을까?


          두번째 문제는 미국의 테스트 환경에서만 발생하는 문제였다. 재현도 최소 30분~ 2시간 정도가 필요한 것으로 보고가 되었고, 한국에서는 재현이 전혀 되지 않았기 때문에, 몇 달동안 별다른 분석을 할 수 없었다.

          매주 고객은 업데이트된 보고서를 요청하는데, 미국에서 테스트한 결과 자료만 가지고는 도무지 원인을 찾을 수 없었다. SIGSEGV로 terminated될 때 출력되는 IP주소를 map파일에서 추적하여 문제 발생 함수명을 찾았지만, 해당함수는 Rose RT쪽 코드로 상용으로 널리 사용되는 부분이라 문제가 발생할 가능성이 아주 적은 곳이었다. 미국에 상주하는 테스트 팀에 요청하여 문제 발생시 core file을 dump하여 문제 발생 위치를 뒤진 결과 스택에 있는 포인터 변수에 이상한 주소 값이 들어가 있어, 해당 포인터 변수에 접근시 SIGSEGV가 발생하는 것이 확인 되었다. 하지만 core file에서 memory를 dump해 보아도 stack overflow의 흔적은 찾을 수 없었고, 해당 변수에 이상한 값이 들어가 있었다. 프로그램의 어디에선가 포인터를 잘못 사용한 것으로 추정되었지만, 수십만 라인의 코드에서 해당 위치를 찾기란 불가능에 가까웠다.

          결국은 문제가 재현되는 미국으로 출장을 갈수 밖에 없었고, 출장을 와서도 특별히 뾰족한 수가 있는 상황이 아니었다. 최후로 선택한 방법은 divide and conquer로 프로그램내의 어플리케이션 모듈을 하나씩 실행되지 않도록 수정하여 재 컴파일 하고 이를 실행하여 문제 발생 여부를 테스트 하였다.

          오랜 시각의 재현 테스트(한번에 40분정도 걸리는...) 끝에 18번째 재현 테스트만에 GPS관련 application을 disable하였을 때 문제가 발생되지 않음이 확인되었고, 해당 모듈을 디자인 리뷰한 결과 해당 프로그램 코드에서 구조체의 항목중 배열로된 어떤 항목을 사용하면서 배열의 index를 무한히 증가시킴을 확인하였다. 따라서 구초체 크기만큼씩 건너뛰면서 메모리에 값이 덮어써졌고, 이 과정이 20분정도 반복되면 그때가 되서야 시스템이 SIGSEGV로 죽었던 것이다.

          이 문제를 발견하기 위해서 한국에서 내가 알고 있는 모든 급 디버깅 기법을 사용하여 문제를 분석하였지만, 전혀 문제의 핵심에 접근하지 못하였다. 메모리 오류를 잡아주는 각종 디버깅용 라이브러리를 사용하여 미국에 테스트를 요청하여도 전혀 문제는 검출되지 않았고, 결국 문제 해결은 문제가 재현되는 환경에서 "노가다"에 가까운 반복 재현 테스트로 문제 발생 원인을 좁혀가는 과정을 통하여 이루어졌다. 다행이도(?) 문제가 한시간 안에 한번은 재현이 되었기 때문에 이러한 방법이 가능하기는 했지만, 그래도 여전히 지난한 과정이었고 "근성"으로 이를 진행할 수 밖에 없었다.

          (개발 환경이 리눅스등의 환경이었으면 좀 더 강력한 메모리 오류 검출 툴들을 사용해 볼 수 있었겠지만, 애석하게도 이런 툴이 돌아가지 못하는 환경이었다. )

          아마도 문제 재현이 더 어려원다면, 문제를 더 자주 재현시키는 다른 방법을 찾아야 했을 것이고, 이는 더욱 지난한 과정이 되었을 것이다. 이런 경우가 발생하지 않은 걸 신에게 감사할 수밖에...

          2009년 3월 2일 월요일

          do(...}while(0)의 또다른 용도

          [#define에서 do{...}while(0)을 사용하는 이유는?] 에서 소개한 이유 외에 do{...}while(0)이 유용하게 사용될 수 있는 경우가 있다. 바로 goto를 사용하지 않고 중첩된 조건문을 탈출하기 위해서 do{...}while(0)을 응용할 수 있다. (옳건 그르건 간에)많은 사람들이 goto를 사용하는 것을 금기시 하기 때문에 do{...}while(0)문은 goto를 써야만 하는 경우에 좋은 대안이 될 수 있다.

          바로 예제를 보고 이해하자.

          if(...)
          {
            if(...)
            {
             ....
             goto ESCAPE;
            }
          }
          ESCAPE:


          위코드는 아래와 같이 goto없이 작성이 가능하다.

          do{
            if(...)
            {
              if(...)
              {
                 ....
                break;
              }
            }
          }while(0);

          2009년 1월 21일 수요일

          라이브러리 이해하기 2 - 공유 라이브러리 버전 관리.

          공유 라이브러리는 보통 아래와 같이 명명된다.

          libMyLibrary.so.1.2.3

          라이브러리는 용도에 따라 몇가지 서로 다른 이름을 가지고 있는데 혼돈되기 쉬우니 주의가 필요하다.

          1. 링커 이름.(linker name)

          링커 이름은 라이브러리 파일 명에서 버젼을 나타내는 숫자를 뺀 so까지의 이름이다. 즉 위에서는 libMyLibrary.so까지가 linker name이 된다. 링커 이름은 해당 라이브러리를 사용하는 프로그램을 빌드 할 때 사용되는 이름이다. 더 정확히 말하면, 프로그램이 빌드될 때 해당 프로그램이 사용하는 공유 라이브러리를 찾기 위해 링커 이름을 가진 파일이 빌드하는 시스템에 있어야 한다. 예를들어 어떤 어플리케이션이 libMyLibrary.so.1.2.3을 사용하여 컴파일 할 경우 빌드 옵션에 -lMyLibrary를 추가하게 되는데 이때 기본 라이브러리 경로나 혹은 –L 옵션을 사용하여 지정한 디렉토리에 libMyLibrary.so 파일, 즉 링커 이름으로 된 라이브러리 파일이 있어야 한다. (보통은 libMyLibrary.so.1.2.3에 대한 심볼링 링크로 이 파일을 만든다 이렇게 하는 이유는 호스트 개발 환경에 해당 라이브러리의 여러 버젼이 존재할 경우 이를 관리하기 위함이다. ) 크로스 컴파일을 하는 환경에서는 빌드시에만 링커 이름을 가진 파일이 필요하며, 타겟에서 실행 시에는 이 파일이 필요치 않다. 즉 개발하는 호스트 컴퓨터에는 라이브러리 파일 이름이 링커 이름으로 되어 있거나 이를 심볼링 링크로 만들어야 한다. 컴파일러/링커는 링커 이름을 가진 파일에서 이 라이브러리의 soname을 읽어오게 된다. 따라서 공유 라이브러리는 빌드시 실행 파일에 포함되지는 않지만 soname을 읽어와야 하기 때문에 공유 라이브러리가 빌드하는 호스트 시스템에 없으면 빌드가 되지 않는다.

          2. soname

          이 이름은 로더가 라이브러리를 실행할 때 찾는 이름으로 타겟에는 soname을 가진 파일이 반드시 있어야 한다. 때때로 이 파일은 심볼릭 링크로 다른 파일로 링크되어 있다.

          일반적으로 soname은 so뒤에 숫자 하나를 추가하여 만든다.  예를 들어  libMyLibrary.so.1 가 위 라이브러리의 soname으로 적합하다. 아래와 같은 명령으로  soname을 지정할 수 있다.

          gcc –shared –Wl,-soname libMyLibrary.so.1 –o libMyLibrary.so.1.2.3 MyLibrary.o

          so뒤에 오는 첫번째 숫자는 라이브러리의 Major version number로 일반적으로 호환성이 변경되는 경우에 증가시킨다. 이렇게 라이브러리를 빌드하고 또 이 라이브러리를 사용하는 프로그램을 빌드하면 해당 프로그램 실행 시 로더에 의해서 soname을 가진 파일을 라이브러리 경로에서 찾게 된다. 즉 프로그램이 실행될 때에는 soname을 가진 라이브러리 파일이 필요하다. 크로스 컴파일을 하는 환경에서는 빌드 시에는 soname을 가진 파일이 아닌 링커이름을 가지는 파일(일반적으로 xxx.so로 끝나는 파일)이 필요하고 실행시에는 soname을 가지는 라이브러리(혹은 이에 대한 심볼릭 링크 )가 필요한 것이다.

          라이브러리 full name에서 두번째 숫자는 라이브러리의 minor version number로 호환성에는 변경이 없지만 새로운 함수등이 추가되는 경우에 변경되며, 세번째 자리는 release 버전으로 버그 수정 등으로 라이브러리가 새로 릴리즈되는 경우에 변경된다.

          따라서 크로스 컴파일 환경에서, 호스트에서 공유 라이브러리 libMyLibrary.so.1.2.3 를 만들어 타겟 시스템에 넣을 경우 타켓 시스템에는 아래와 같이 soft link file을 만들어 soname을 가진 파일을 만들어야 한다.

          ln –s libMyLibrary.so.1.2.3 libMyLibrary.so.1
          or
          mv libMyLibrary.so.1.2.3 libMyLibrary.so.1


          또한 해당 라이브러리를 사용하여 프로그램을 만드는 모든 개발자는 자신의 빌드 환경에 아래와 같이 공유 라이브러리가 링커 이름을 가지도록 설정하여야 한다. (즉 .so.1 파일은 호스트(빌드)시스템에서는 필요 없다)

          ln –s libMyLibrary.so.1.2.3 libMyLibrary.so
          or
          mv libMyLibrary.so.1.2.3 libMyLibrary.so

          특정 라이브러리의 soname을 확인하기 위해서는 아래와 같은 명령을 사용할 수 있다.

          objdump -p libMyLibrary.so.1.2.3 | grep SONAME

          더 자세한 내용은...
          http://wiki.kldp.org/wiki.php/DocbookSgml/Program-Library-HOWTO#DL-LIBRARIES