Programming/C2009. 10. 15. 00:23
#define putchar(c) outbyte(c)   
static void printchar(char **str, int c)   
{   
 extern int putchar(int c);   
 if (str) {   
  **str = c;   
  ++(*str);   
 }   
 else (void)putchar(c);   
}   
#define PAD_RIGHT 1   
#define PAD_ZERO 2   
static int prints(char **out, const char *string, int width, int pad)   
{   
 register int pc = 0, padchar = ' ';   
 if (width > 0) {   
  register int len = 0;   
  register const char *ptr;   
  for (ptr = string; *ptr; ++ptr) ++len;   
  if (len >= width) width = 0;   
  else width -= len;   
  if (pad & PAD_ZERO) padchar = '0';   
 }   
 if (!(pad & PAD_RIGHT)) {   
  for ( ; width > 0; --width) {   
   printchar (out, padchar);   
   ++pc;   
  }   
 }   
 for ( ; *string ; ++string) {   
  printchar (out, *string);   
  ++pc;   
 }   
 for ( ; width > 0; --width) {   
  printchar (out, padchar);   
  ++pc;   
 }   
 return pc;   
}   
/* the following should be enough for 32 bit int */  
#define PRINT_BUF_LEN 12   
static int printi(char **out, int i, int b, int sg, int width, int pad, int letbase)   
{   
 char print_buf[PRINT_BUF_LEN];   
 register char *s;   
 register int t, neg = 0, pc = 0;   
 register unsigned int u = i;   
 if (i == 0) {   
  print_buf[0] = '0';   
  print_buf[1] = '\0';   
  return prints (out, print_buf, width, pad);   
 }   
 if (sg && b == 10 && i < 0) {   
  neg = 1;   
  u = -i;   
 }   
 s = print_buf + PRINT_BUF_LEN-1;   
 *s = '\0';   
 while (u) {   
  t = u % b;   
  if( t >= 10 )   
   t += letbase - '0' - 10;   
  *--s = t + '0';   
  u /= b;   
 }   
 if (neg) {   
  if( width && (pad & PAD_ZERO) ) {   
   printchar (out, '-');   
   ++pc;   
   --width;   
  }   
  else {   
   *--s = '-';   
  }   
 }   
 return pc + prints (out, s, width, pad);   
}   
static int print(char **out, int *varg)   
{   
 register int width, pad;   
 register int pc = 0;   
 register char *format = (char *)(*varg++);   
 char scr[2];   
 for (; *format != 0; ++format) {   
  if (*format == '%') {   
   ++format;   
   width = pad = 0;   
   if (*format == '\0') break;   
   if (*format == '%') goto out;   
   if (*format == '-') {   
    ++format;   
    pad = PAD_RIGHT;   
   }   
   while (*format == '0') {   
    ++format;   
    pad |= PAD_ZERO;   
   }   
   for ( ; *format >= '0' && *format <= '9'; ++format) {   
    width *= 10;   
    width += *format - '0';   
   }   
   if( *format == 's' ) {   
    register char *s = *((char **)varg++);   
    pc += prints (out, s?s:"(null)", width, pad);   
    continue;   
   }   
   if( *format == 'd' ) {   
    pc += printi (out, *varg++, 10, 1, width, pad, 'a');   
    continue;   
   }   
   if( *format == 'x' ) {   
    pc += printi (out, *varg++, 16, 0, width, pad, 'a');   
    continue;   
   }   
   if( *format == 'X' ) {   
    pc += printi (out, *varg++, 16, 0, width, pad, 'A');   
    continue;   
   }   
   if( *format == 'u' ) {   
    pc += printi (out, *varg++, 10, 0, width, pad, 'a');   
    continue;   
   }   
   if( *format == 'c' ) {   
    /* char are converted to int then pushed on the stack */  
    scr[0] = *varg++;   
    scr[1] = '\0';   
    pc += prints (out, scr, width, pad);   
    continue;   
   }   
  }   
  else {   
  out:   
   printchar (out, *format);   
   ++pc;   
  }   
 }   
 if (out) **out = '\0';   
 return pc;   
}   
/* assuming sizeof(void *) == sizeof(int) */  
int printf(const char *format, ...)   
{   
 register int *varg = (int *)(&format);   
 return print(0, varg);   
}   
int sprintf(char *out, const char *format, ...)   
{   
 register int *varg = (int *)(&format);   
 return print(&out, varg);   
}   
#ifdef TEST_PRINTF   
int main(void)   
{   
 char *ptr = "Hello world!";   
 char *np = 0;   
 int i = 5;   
 unsigned int bs = sizeof(int)*8;   
 int mi;   
 char buf[80];   
 mi = (1 << (bs-1)) + 1;   
 printf("%s\n", ptr);   
 printf("printf test\n");   
 printf("%s is null pointer\n", np);   
 printf("%d = 5\n", i);   
 printf("%d = - max int\n", mi);   
 printf("char %c = 'a'\n", 'a');   
 printf("hex %x = ff\n", 0xff);   
 printf("hex %02x = 00\n", 0);   
 printf("signed %d = unsigned %u = hex %x\n", -3, -3, -3);   
 printf("%d %s(s)%", 0, "message");   
 printf("\n");   
 printf("%d %s(s) with %%\n", 0, "message");   
 sprintf(buf, "justif: \"%-10s\"\n", "left"); printf("%s", buf);   
 sprintf(buf, "justif: \"%10s\"\n", "right"); printf("%s", buf);   
 sprintf(buf, " 3: %04d zero padded\n", 3); printf("%s", buf);   
 sprintf(buf, " 3: %-4d left justif.\n", 3); printf("%s", buf);   
 sprintf(buf, " 3: %4d right justif.\n", 3); printf("%s", buf);   
 sprintf(buf, "-3: %04d zero padded\n", -3); printf("%s", buf);   
 sprintf(buf, "-3: %-4d left justif.\n", -3); printf("%s", buf);   
 sprintf(buf, "-3: %4d right justif.\n", -3); printf("%s", buf);   
 return 0;   
}   

printf의 소스... 천천히 봐볼까나...
Posted by skensita
Programming/C2009. 10. 15. 00:05
char * ltoa (long val, char *buf, unsigned radix)
{
char *p; /* pointer to traverse string */
char *firstdig; /* pointer to first digit */
char temp;      /* temp char */
unsigned digval; /* value of digit */

p = buf;

if (radix == 10 && val < 0) {
   /* negative, so output '-' and negate */
   *p++ = '-';
   val = (unsigned long)(-(long)val);
}

firstdig = p;   /* save pointer to first digit */

do {
   digval = (unsigned) (val % radix);
   val /= radix;       /* get next digit */

   /* convert to ascii and store */
   if (digval > 9)
*p++ = (char) (digval - 10 + 'a');  /* a letter */
   else
*p++ = (char) (digval + '0');       /* a digit */
} while (val > 0);

/* We now have the digit of the number in the buffer, but in reverse
  order.  Thus we reverse them now. */

*p-- = '\\0';    /* terminate string; p points to last digit */

do {
   temp = *p;
   *p = *firstdig;
   *firstdig = temp;   /* swap *p and *firstdig */
   --p;
   ++firstdig; /* advance to next two digits */
} while (firstdig < p); /* repeat until halfway */

return buf;
}

출처 : http://cpueblo.com/programming/cpp/contents/99.html
Posted by skensita
Programming/C2008. 12. 7. 21:59

#define SWAP(x, y) {(x)^=(y)^=(x)^=(y);}

위와같은 SWAP 매크로가 있다.

이제 이 매크로를 한번 풀어 보겠다.

int main()
{

int x = 10;
int y = 20;

printf("X = %d, Y = %d", x, y);

x ^= y ^= x ^= y;  //SWAP(x, y);

printf("X = %d, Y = %d", x, y);

return 0;

}

위과 같이 매크로가 풀릴것인데... 이것을 하나씩 뜯어보면

제일 뒤에것 부터 x ^= y;

이걸 다시 풀어쓰면 x = x^y;

자 그럼 x와 y를 2진수로 풀어 보겠다.

x : 00001010 (10진수 : 10)
y : 00010100 (10진수 : 20)

^모양은 XOR하란 말이므로 위의 두 2진수를 XOR 시키면

x^y = 00011110

그럼 이 값이 x에 들어갔을 것이다.

그럼 다음인 y ^= x;

이것도 다시 풀어쓰면 y = y^x;

x : 00011110
y : 00010100

XOR 시키겠다.

y^x = 00001010

이 값 또한 y에 들어가게 된다.

이제 마지막으로 x ^= y;

풀어보면 x = x^y;

x : 00011110
y : 00001010

XOR 시키겠다.

x^y = 00010100

x에 마지막으로 값이 들어 갔다.

그럼 최종적인 값을 확인해 보겠다.

x : 00010100 (10진수 : 20)
y : 00001010 (10진수 : 10)

자 어떤가...

추가적인 변수 없이 비트연산만으로 SWAP구현이 가능하다.

보기쉽게 코드화 하면

x = x ^ y;
y = y ^ x;
x = x ^ y;


과 같이 쓸 수 있다.


또하나의 방법을 보겠다.

x = x + y;
y = x - y;
x = x - y;

이정도는 +, - 만 할줄 안다면 풀이가 가능하니

풀이는 하지 않겠다...

이처럼 여러가지 방법으로 추가적인 변수 없이 SWAP 구현이 가능하다.

Posted by skensita
Programming/C2008. 12. 7. 18:46
논리회로에서 나오는 용어들입니다.
일종의 연산자 입니다.

TRUE(참 : 1), FALSE(거짓 : 0)
즉 논리연산에선 참과 거짓의 데이터만 존재합니다.(디지털이므로)
우리가 방에 들어가 전기를 켤때 불이 들어오면 True(1), 불을 끄면 False(0)입니다.

* NOT(!)은 True면 False, False면 True입니다.

* AND(&&)는 둘 다 모두 True일때 결과값이 True입니다

x y result
-------------------
T T True
T F False
F T False
F F False


* OR(||)은 둘 다 모두 False일때 결과값이 False입니다
(즉 둘중 어느 하나라도 True이면 결과값은 True입니다)

x y result
-------------------
T T True
T F True
F T True
F F False


* XOR(^)은 두 값이 서로 다를때만 True결과값을 가집니다.

x y result
-------------------
T T False
T F True
F T True
F F False
Posted by skensita
Programming/C2008. 12. 6. 21:51

사실 별로 재미있지는 않습니다.

(이강좌는 8bit 마이크로컨트롤러에서 사용하는 것을 기준으로  char은 8bit  int는 16bit 로 가정하고 진행 됩니다. ) 

그래도 중요하고 꼭 알고 넘어가야 하는 문제이기 때문에..

 

비트연산자에 대해 공부 해 보겠습니다.

 

비트연산자는

&

| (Shift + \ 입니다. 모음 ㅣ(이),  L(엘)의 소문자, 대문자 I(아이) 가 아닙니다. )

^

~

<<

>>

 

이렇게 6가지가 있습니다.

 

 &  (AND)

 둘다 1이면 1

 0 & 0  =>

 0 & 1  =>

 1 & 0  =>

 1 & 1  =>

0

0

0

1

 |  (OR)

 둘중 하나이상 1이면 1

 0 |  0  =>

 0 |  1  =>

 1 |  0  =>

 1 |  1  =>

0

1

1

1

 ^  (XOR)

 둘이 서로 다르면 1

 0 ^  0  =>

 0 ^  1  =>

 1 ^  0  =>

 1 ^  1  =>

0

1

1

0

 ~  (NOT)

 1이었으면 0, 0이었으면 1

~0  =>

~1  =>

1

0

 <<

 왼쪽으로 비트 이동

0b00000011 << 3 

0b00011000 

 >>

 오른쪽으로 비트 이동

0b01100011 >> 2 

0b00011000 

 

어려운것은 없죠?

한가지 알고 넘어가야 할 부분은  << 나 >> 로 이동할때 이동한 만큼의 비트가 버려지고 0으로 채워 집니다.

잘 모르시겠다구요? 그럼 한번 예를 들어 보겠습니다.

 

unsigned char num=0xFF;

num= num<<4;

 

num은 얼마가 될까요?? 

바로 0xF0;

2진수로 풀어 보겠습니다.

0xFF==0b11111111 입니다.

0b 1111 1111              을 왼쪽으로 4번 비트 이동 하겠습니다.

0b 1111 1111 0000      입니다. 비게 되는 오른쪽은 0으로 채워집니다.  그리고 빨간 1111은 버려지게 됩니다.

왜 버려지냐구요? 바로 변수 선언을 8bit 로 했기 때문이죠.. 빨간 1111을 저장할 공간이 없기 때문에 버려집니다.

그래서 남은건 0b 1111 0000 == 0xF0

 

만약 num 가 int 형 이고 초기값이 0x00FF 였다면  어떻게 되었을까요??

그렇죠.. 0x0FF0 이 됩니다.

쉽죠??

 

그럼 각 연산자들이 어떤 상황일때 자주 쓰이는지 알아 보겠습니다.

 

 

먼저

& 연산자

& 연산자는 둘다 1이어야 1이 되기 때문에

1)특정 비트만 0으로 만들고 싶을때

2)특정 비트만 확인 하고 싶을때 (masking)

로 많이 사용됩니다. 

예를 들어 보겠습니다.

1)특정 비트만 0으로 만들고 싶을때

PORTA 에 LED8개가 붙어 있고 모두 켜져 있습니다.(PORTA==0xFF) 

그런데 다른 비트는 건드리지 않고 0번 1번 7번 비트의 LED 만 끄고 싶다면???

PORTA=PORTA & 0x7C;

라고 하면 됩니다.

쓰고 보니 PORTA=0x7C; 라고 하면 되지 않느냐? 라고 물으실 분들을 위해 다른 가정을 하나 더 넣겠습니다.

현재 LED 가 어떻게 켜져 있는지 모르는데 0번 1번 7번 비트의 LED 만 끄고 싶다면??? 그렇다면..

PORTA=PORTA & 0x7C; 이런식으로 & 연산자를 쓰는 것이 다른 비트는 건드리지 않게 됩니다.

2)특정 비트만 확인 하고 싶을때 (masking)

특정 비트만 확인하는 부분은 조건문에서 많이 들어 가는데..

예를 들어 PORTA 에 스위치가 8개 붙어 있는데

PORTA 3번 비트에 달려 있는 스위치가 눌렸는지 판단하고 싶다면?? (스위치는 안눌렀을때 1 눌리면 0으로 가정합니다. )

if((PINA & 0x08) == 0){  ... } 

else 

 이런식으로 사용하게 되면 스위치가 눌렸다면 {  ... }  의 동작을 수행하고 눌리지 않았다면  else 문을 수행 하겠죠??

 

 

 

| 연산자

| 연산자는 둘중 하나라도 1이면 1이 되기 때문에

1)특정 비트만 1으로 만들고 싶을때

로 많이 사용됩니다. 

 

예를 들어 보겠습니다.

1)특정 비트만 1으로 만들고 싶을때

AVR의 ADC관련 레지스터 중에 ADCSRA 라는 레지스터가 있습니다.

그런데 이 레지스터의 6번 비트를 1로 set 하면 ADC를 시작하라는 명령을 내리는 비트입니다.  다른 비트들은 ADC 관련 설정 값이죠..

만약.. ADC를 시작하고 싶은데 ADC 의 다른 설정은 건드리지 말아야 할때.. 바로 | 를 쓰면 됩니다.

ADCSRA |= 0x40; 이렇게 사용하면 해당 비트만 1로 만들수 있습니다.

포트 출력할때 특정포트만 1로 만들고 싶다면 동일한 방법으로 사용하면 됩니다.

 

 

^ 연산자 (XOR 발음 익스클루시브오알 ㅡㅡ;)

두개가 다르면 1이 되기 때문에

1)특정 비트를 토글하고 싶을때

많이 사용됩니다. 

 

예를 들어 보겠습니다.

PORTA 에 연결된 LED중 0번 비트에 연결된 LED만 켰다 껏다를 반복하고 싶습니다.

그럼 이렇게 코드는 어떻게 작성할까요??

while(1)

{

     PORTA = 0x01;

     delay_ms(1000);

     PORTA = 0x00;

     delay_ms(1000);

}

이렇게 작성해도 되지만  ^ 를 사용하면

while(1)

{

     PORTA ^= 0x01;       

     delay_ms(1000);

}

코드가 간단해 집니다.

PORTA 의 값이 뭐든 다른 비트는 변화 없이 0번 비트만 토글됩니다.

 

 

<<, >> 연산자

& 연산자는 둘다 1이어야 1이 되기 때문에

1)두개의 8bit 정수를 합쳐서 int 형 변수에 넣어 줄때  

2)나누기나 곱셈대신 사용할때

 

예를 들어 보겠습니다.

AVR 에는 10bit ADC 가 있습니다.

ADC 를 하게 되면 10bit 를 8bit씩 나눠 ADCH, ADCL 에 나눠 담기게 됩니다.

10bit 니까 8 , 2나 2 , 8로 나뉘게 됩니다.  (나뉘는 방법은 ADMUX 레지스터 안에 있는 ADLAR 비트에 의해 설정됩니다.)

 

 

 

ADLAR==0 일때 shift 연산자를 이용해서 16비트 변수에 담아 보겠습니다.

원칙적으로 A/D 변환결과를 읽을 때는 ADCL 부터 읽어야 합니다.

 

unsigned int temp=0x0000;

temp =  ADCL | (ADCH<<8);     //이렇게 써도 됨  temp =  ADCL + (ADCH*256);

 

ADLAR==1 일때도 한번 해보겠습니다.

 

unsigned int temp=0x0000;

temp =  (ADCL >>6 )  | (ADCH<<2) ;

 

벌써 비트 연산자가 막 들어가기 시작하네요...

곱셈이나 나눗셈을 대신 할때는 몇가지 조건이 있습니다.

먼저 정수형만 되고 2의 지수승만 됩니다.   2,4,8,16,32,64,128,256,512,1024 .....

예를 들어보겠습니다.

0x0F 를 <<2 한것과 *4 것을 비교해 보겠습니다.

0x0F 는 10진수로 15입니다.          *4하면 60 이죠

0x0F를  <<2 하면 0x3C 입니다.     십진수로 계산해 보면 60이죠

 

아래 표를 보면 << 와 *과의 관계가 이해가 되실 겁니다.

 <<1

*2

 <<2

*4

 <<3

*8 

 <<4

*16 

>> 와 / 도 마찬가지입니다.

 >>1

/2

 >>2

/4

 >>3

/8 

 >>4

/16 

 * 나 / 대신  <<, >> 를 쓰는 이유는 연산하는 속도가 빠르기 때문입니다.

 2,4,8,16,32,64,128,256,512,1024 ... 으로 나누거나 곱할때는 << 나 >> 를 쓰는 연습을 해 봅시다.  

 

 

이것으로 비트연산 강좌를 마칩니다. ^^ 모두 열공~

Posted by skensita
Programming/C2008. 12. 6. 21:21

1. 임시 변수 없는 SWAP 매크로

#define SWAP(a, b) {(a)^=(b)^=(a)^=(b);} 


2. 주어진 수가 2의 제곱수(1, 2, 4, 8, 16 등등)인지 검사(출처: flipCode)

inline bool IsPow2(int v)
{
    return (!(v & (v - 1)));
}


3. 0이 아닌 비트들의 개수 세기...(예를 들어 0xf0는 4개..)(출처: flipCode)

8비트용:
v = (v & 0x55) + ((v >> 1) & 0x55);
v = (v & 0x33) + ((v >> 2) & 0x33);
return (v & 0x0f) + ((v >> 4) & 0x0f);

32비트용:
#define g31 0x49249249ul // = 0100_1001_0010_0100_1001_0010_0100_1001
#define g32 0x381c0e07ul // = 0011_1000_0001_1100_0000_1110_0000_0111


v = (v & g31) + ((v >> 1) & g31) + ((v >> 2) & g31);
v = ((v + (v >> 3)) & g32) + ((v >> 6) & g32);
return (v + (v >> 9) + (v >> 1 + (v >> 27)) & 0x3f; 

Posted by skensita
Programming/C2008. 11. 18. 16:59

0.시작하면서...

 OS를 개발하면서 초반에 어셈블리어로 작성한 코드를 보면, 사용한 어셈블리어 명령이 몇 종류 없는 것을 알 수 있다. 그것도 아주 기초적인 수준의 어셈블리어만 사용했는데, 역으로 말하면 몇가지 종류의 어셈블리어만 알고 있으면 부트로더(Boot Loader), 커널로더(Kernel Loader), 그리고 기타 초기화 함수를 작성할 수 있다.



1.어셈블리어(Assembly Language) 기초 명령

 아래는 기초 명령의 리스트이다(Intel Style의 명령이라 가정한다).


  • mov A, B : B에서 A로 값을 이동
  • cmp A, B : 두 값을 비교하여 결과를 Flags 레지스터에 업데이트
  • rep instruction : insturction을 CX 레지스터의 값 만큼 반복 수행
  • call X : Stack에 Return Address를 삽입하고 jump 수행
  • jmp X : 무조건 해당 주소로 jump
  • je, ja X : 조건 분기 명령. Flags 레지스터의 플레그 값에 따라서 jmp 수행(보통 cmp와 같은 명령어와 함께 사용)
  • push X: 스택에 값을 저장
  • pusha, pushad : 스택에 모든 레지스터 값을 저장. EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI 저장
  • pop X : 스택에서 값을 꺼냄
  • popa, popad : 스택에서 모든 레지스터의 값을 꺼냄. 위의 pushad 명령과 같은 순서의 레지스터 사용
  • add A, B : A에 B의 값을 더함
  • sub A, B : A에서 B의 값을 뺌
  • mul A, B : A에 B의 값을 곱함
  • inc A : A의 값을 1 증가시킴
  • int X : X번째의 Software Interrupt를 발생시킴
  • ret, retn : Stack에 포함된 Return Address를 꺼내서 해당 주소로 복구(보통 Call 명령과 같이 사용)
  • iret, iretd : 인터럽트 처리 시에 모든 처리를 완료하고 다시 태스크로 복구
  • or A, B : A에 B값을 OR
  • xor A, B : A에 B값을 XOR
  • not A : A의 값을 반전(0->1, 1->0)
  • lgdt : GDT를 설정(Intel Architecture 특수 명령)
  • lidt : IDT를 설정(Intel Architecture 특수 명령)
  • lldt : LDT를 설정(Intel Architecture 특수 명령)
  • ltr : Task Register에 TSS를 설정(Intel Architecture 특수 명령)
  • clts : Task Switching 플래그를 0으로 설정(Intel Architecture 특수 명령)
  • cli : 인터럽트 불가 설정
  • sti : 인터럽트 가능 설정
  • fninit : FPU 초기화 명령(x87 Floating Point Unit 관련 명령)
  • ... 기타 등등

 물론 전부를 나열하지는 않았지만 척 봐도 알 수 있는 기본적인 명령어들이다. 물론 성능을 고려한다면 더 많은 어셈블리어 명령어들이 리스트에 포함되겠지만, 성능적인 면을 고려하지 않는다면 위의 함수 정도면 OK다.

 위의 함수에 대한 기본적인 기능들은 Intel Architecture Manual Volume 2 Instruction Set 문서를 참고하면 된다. 위의 명령어를 사용하여 프로그램을 작성하고 싶은 사람은 New Wide Assembler(NASM)을 이용하면 테스트 가능한데, 아래의 참고자료를 참고하면 간단히 함수를 생성하고 빌드 할 수 있다.


 http://nasm.sourceforge.net/ 홈페이지에 가면 컴파일러를 다운받을 수 있고 예제 및 문서도 제공하므로 한번 해보는 것도 괜찮을 듯 하다.


2.호출 규약(Calling Convension)

2.1 stdcall, cdecl, fastcall

 실제로 어셈블리어를 아는 것도 중요하지만 이 함수를 C 언어에서 어떻게 호출하여 사용할 것인가 하는 문제도 중요하다. 흔히들 호출 규약(Calling Convention)이라고 표현하는 이것은 함수를 호출하는 규약인데, 몇가지 방식이 존재한다.


  • stdcall(pascal) 방식 : 스택에 파라메터를 역순으로 삽입하고 함수를 호출. 스택의 정리작업을 호출된 함수에서 수행. 파스칼 언어 및 베이직 언어에서 사용하는 방식
  • cdecl 방식 : 스택에 파라메터를 넣는 방식은 stdcall과 같음. 단 스택의 정리작업을 호출한 함수에서 수행. C언어에서 사용하는 방식
  • fastcall 방식 : 몇개의 파라메터는 레지스터를 통해 넘기고 나머지 파라메터는 스택을 사용하는 방식

 위의 세가지 중에서 보편적인 방식 두가지는 stdcall 및 cdecl 방식이다. 이 두가지 방식의 가장 큰 차이점은 스택의 정리를 누가 하는 가이다.

 stdcall 방식 같은 경우 Callee(호출 된 함수)에서 스택 정리를 하므로 Caller(호출하는 함수)와 Callee 모두 파라메터의 개수를 알고 있어야 정상적인 처리가 가능하다.

 반면 cdecl 방식 같은 경우 Caller에서 스택 정리를 하므로 Callee는 파라메터의 개수를 정확하게 몰라도 된다. 바로 이 점이 C 언어의 가변인자(Variable Argument)를 가능하게 하는 것이다(printf와 같은 함수를 생각해보자).

 가변인자에 대해서는 나중에 알아보고 우리가 사용할 cdecl에 대해서 자세히 알아보자.


2.2 cdecl 분석

 아래는 간단한 C 프로그램을 작성한 것이다.


int DoSomething( int a, int b )
{
    int c;
    c = a+b;
    return c;
}

int main(int argc, char* argv[])
{
    DoSomething( 1, 2 );

}

 간단하게 파라메터 2개를 받아서 그중 첫번째 파라메터를 리턴하는 함수이다. 이것을 cdecl로 해서 컴파일 한 결과 나온 어셈블리어 결과는 아래와 같다.


int DoSomething( int a, int b )
{
    int c;
    c = a+b;
    return c;
   /* 여기가 어셈블리어로 변경된 코드
 
   push ebp
    mov ebp,esp
    push ecx
    mov eax,[ebp+08h]
    add eax,[ebp+0Ch]
    mov [ebp-04h],eax
    mov eax,[ebp-04h]
 
   mov esp,ebp
    pop ebp
    retn
   */
}

int main(int argc, char* argv[])
{
    DoSomething( 1, 2 );
   /* 여기가 어셈블리어로 변경된 코드
    push ebp
    mov ebp,esp
    push 00000002h
    push 00000001h
    call SUB_L00401000
   
add esp,00000008h <== 스택을 정리하는 부분
    pop ebp
    retn
   */
}



 위에서 보면 파라메터를 역순으로 Push 하는것을 알 수 있으며 main 함수에서 "add esp,08" 명령을 통해 스택 정리를 수행함을 볼 수 있다. 여기서 주의해서 봐야 할 부분은 DoSomething 함수에서 어떻게 파라메터에 접근하고 또한 어떻게 함수 내부적으로 사용하는 레지스터를 관리하고 복원하는가 이다.


 아래는 Caller(main)Callee(DoSomething)의 스택의 상태를 표시한 것이다.


 

사용자 삽입 이미지


<Caller와 Callee의 Stack>


 왜 ESP로 접근하지 않고 EBP를 통해 파라메터에 접근하는 것일까? 위의 그림을 보면 왜 ebp + Index로 접근을 하는 지 알 수 있다. 스택의 Top을 의미하는 ESP 레지스터의 경우 코드 중간 중간에 스택을 사용하면서 계속 변하는 값이다. 그 반면에 파라메터의 위치는 항상 고정적이므로 스택의 Top을 이용해서 파라메터에 접근하려면 문제가 발생한다. 따라서 EBP에 ESP의 값을 처음 설정해 두고 EBP를 이용해서 고정된 Offset으로 접근하는 것이.


 이와 같이 하면 스택의 Top이 계속 바뀌더라도 EBP가 초기의 스택 Top의 위치를 가지고 있으므로 EBP + 8, EBP+ 12과 같은 값으로 접근 가능하다. 위에서 초기에 Callee의 Stack에서 Push ebp를 하고 난 뒤에 Stack의 Top은 esp1을 가르키고 있다. 이 값을 ebp에 넣게 되므로 ebp를 이용하면 Parameter에 고정된 Index( 8, 12, 16...)으로 접근을 할 수 있는 것이다.


2.3 stdcall

stdcall의 경우에는 cdecl과 거의 차이가 없고 스택을 정리하는 부분만 차이가 있다.

int DoSomething( int a, int b ) 

    int c; 
    c = a+b; 
    return c; 
   /* 여기가 어셈블리어로 변경된 코드
    push ebp
    mov ebp,esp
    push ecx
    mov eax,[ebp+08h]
    add eax,[ebp+0Ch]
    mov [ebp-04h],eax
    mov eax,[ebp-04h]
    mov esp,ebp
    pop ebp
    retn 08h <== 스택을 정리하는 부분
   */ 
}   
int main(int argc, char* argv[]) 

    DoSomething( 1, 2 ); 
   /* 여기가 어셈블리어로 변경된 코드
    push ebp
    mov ebp,esp
    push 00000002h
    push 00000001h
    call SUB_L00401000
    pop ebp
    retn
   */ 
}  


2.4 프롤로그(prologue) 및 에필로그(epilogue)


 Callee의 스택을 다시 Caller의 스택으로 복원해야 하는데 스택 top을 저장하고 복원하고 하는 작업을 프롤로그(prologue), 에필로그(epilogue)라고 한다. 위에서 본 스택을 복구하는 작업이다.

 만약 우리가 어셈블리어 함수를 만든다면? 그리고 그 함수를 C에서 호출한다면? 아니면 그 반대의 경우라면 어셈블리어 함수를 어떻게 만들어야 할까? 그렇다. 위에서 본 것과 같은 형태 즉 cdecl의 형태를 그대로 따라서 만들면 된다.

 DoSomething() 함수의 프롤로그 에필로그 형태는 아주 일반적인 형태이므로 알아두도록 하자.(꼭 저렇게 구성할 필요는 없지만 일반적이므로 알아두자.)


3.마치면서...

 자 오늘 우리는 어떻게 어셈블리어 함수를 만들어서 파라메터를 넘겨 받을 것이며, 어셈블리어 함수에서 C 함수를 어떻게 호출해야 하는지, 혹은 그 반대의 경우 어떻게해야 하는지에 대해서 알아보았다.




참고자료

1. NASM에서 Macro 사용법

NASM에서 Macro의 작성법은 아래와 같다.

Macros
The built-in macro system has the following syntax:

.macro <name> <args>
<operations>
.endm

Example:


.macro write string  
movw string, %si  
call printstr.
endm


This would be equivalent to the NASM macro:

code : %macro write 1  
          mov si, %1  
          call printstr
          %endmacro


상당히 유용한 기능인것 같다. 코드가 확 줄어버리네. @0@


2. NASM에서 C에서 호출 가능한 함수 작성법

크게 세부분으로 나눌 수 있다.

  • bit 정의 부분
  • global 설정 부분
  • section 설정 부분

각 부분을 정의하는 코드는 아래와 같다.

  1. [bits 32]     <= bit 설정 부분
  2. global _kInit <= 외부로 export할 함수 또는 변수명
  3. extern _main  <= 어셈블리어 파일에서 C 함수 또는 다른 함수를 호출할때 외부에 있다는 것을 알림
  4. section .text <= 섹션 정의부, 코드 영역임을 알림
  5. _kInit:       <= 함수부
       mov      ax, 0x10
       mov      ds, ax
  6.    call     main
  7.    retn 

위와 같이 쓰면 사용할 수 있다.


출처(http://kkamagui.springnote.com
)

Posted by skensita