2013년 2월 5일 화요일

MASM 팁

어셈블리 뻘짓 하던 내용을 정리해서 포스팅합니다.
나중에 또 뻘짓할게 뻔하기에... @,.@


.686
.model flat, StdCall
option casemap:none

.code

; int __stdcall test_sum(int _param1, int _param2)
test_sum PROC StdCall _param1, _param2
 mov eax, _param1
 add eax, _param2
 ret
test_sum ENDP

; int __stdcall test_sum2(int _param1, int _param2)
test_sum2 PROC StdCall _param1, _param2
 mov eax, _param1
 add eax, _param2
 ret 08h
test_sum ENDP

end

이렇게 코드를 작성하면 매크로 어셈블러가 자동으로 prologue, epilogue 를 생성해주기 때문에 test_sum, test_sum2 함수는 모두 아래와 같은 동일한 코드로 변환됩니다.

push ebp
mov  esp, ebp
mov  eax, dword ptr [ebp+08h]
add  eax, dword ptr [ebp+0ch]
leave
ret  08h

어셈블리 코드에서 test_sum 함수는 ret 명령의 경우 파라미터 스택 사이즈를 지정하지 않았지만 ret 08h 로 자동으로 지정해주죠. 편합니다.

하지만 때때로 prologue / epilogue 를 자동생성하지 않게 해야 하는 경우도 있는데 이때는  option prologue, epilogue 로 함수 부분을 감싸주면 됩니다. 아래 test_sum3 함수처럼요.

.686
.model flat, StdCall
option casemap:none

.code

; int __stdcall test_sum(int _param1, int _param2)
test_sum PROC StdCall _param1, _param2
 mov eax, _param1
 add eax, _param2
 ret
test_sum ENDP

; int __stdcall test_sum2(int _param1, int _param2)
test_sum PROC StdCall _param1, _param2
 mov eax, _param1
 add eax, _param2
 ret 08h
test_sum ENDP


option prologue:none
option epilogue:none

; int __stdcall test_sum3(int _param1, int _param2)
test_sum3 PROC Stdcall _param1, _param2
 ; build stack frame
 push ebp
 mov  ebp, esp
 
 mov  eax, dword ptr [ebp+08h]
 add  eax, dwrod ptr [ebp+0ch]
 
 ; restore stack frame
 mov  esp, ebp
 pop  ebp
 ret  08h
test_sum3 ENDP

option prologue:PrologueDef
option prologue:EpilogueDef

end

이상 허접한 팁이었습니다.
MSDN 의 MASM 레퍼런스에는 option 에 대한 키워드는 있는데, 각각의 옵션 키워드에 대한 설명이 없네요!! 왜 그런거지...

2013년 2월 4일 월요일

C4018 워닝을 잡아라!

C4018 warning을 아시나요?
Visual Studio 에서 뿌려주는 컴파일 워닝 코드입니다. (cl.exe 가 뿌려주는거라고 말하는게 정확한 것이겠죠?)
어찌되었거나 Level3 짜리 워닝인데요, "signed int 와 unsigned int 간의 비교연산이 있다" 라는 경고입니다.

여러분은 어떤가요? 컴파일러가 이런 워닝을 뱉어내면 어떻게 하시나요?
"워닝은 반드시 잡아야 하는 대상이 아닌 그냥 무시해도 되는거..." 정도로 생각하시는 분들이 생각보다 많더군요.
C4018 때문에 발생하는 재앙(?)을 한번 볼까요?


void test_compare_uint_int()
{
    int32_t  i32;
    uint32_t u32;

    i32 = 1;
    u32 = 10;  
    printf("i32 %s u32 \n", i32 > u32 ? ">" : "<");

    i32 = -1;
    u32 = 10;
    printf("i32 %s u32 \n", i32 > u32 ? ">" : "<");
}

위의 코드는 i32(signed) 와 u32(unsigned) 간의 비교연산을 시도하는 코드입니다.
실행해보면 어떤 결과가 나올까요?
첫 번째는 비교는 사실 문제가 없습니다.
두 번째는 어쩌면 생각과는 다른 결과가 나올 수 도 있을 겁니다.
상식적으로 -1과 10 중에 누가 더 클까요? 사람은 10이 더 크다고 생각하겠지만 컴퓨터도 그럴까요?
-1 == 0xFFFFFFFF, 10 == 0x0000000a 입니다. 누가 더 큰가요? 당연히 0xFFFFFFFF 입니다.
이게 결국 의도하지 않은 버그를 만들어내게 되지요.
이런 류의 버그는 평소엔 잠잠히 있다가 아주 가끔씩, 비 정기적으로 (보통때는 양의 정수이다가 아주 가끔 음수값이 들어올때) 시스템을 망가뜨리게 되죠.
당연히 디버깅하기가 매우 까다로운 녀석중에 하나입니다.

그럼 어떻게 해야 할까요?
정석대로라면 signed 와 unsigned 를 용도에 따라서 정확히 구분해서 사용하는거죠.
하지만 그게 말처럼 간단하지 않습니다. 애매한 경우가 많이 생기죠.
외부 라이브러리가 리턴하는 값이 signed 인데 내 코드의 비교 대상 변수는 unsigned 라든지...

그럼 어쩌라구?!
그렇습니다. signed 변수와 unsigned 변수를 모두 담을 수 있는 크기의 변수로 캐스팅하는겁니다.

i32 = -1;
u32 = 10;
/* 이렇게 하면 안되요!! */
printf("i32 %s u32 \n", i32, i32 > u32 ? ">" : "<", u32);

/* 이렇게 해야 합니다. */
printf("i32 %s u32 \n", i32, INT64(i32) > INT64(u32) ? ">" : "<", u32);

오늘의 교훈! 컴파일러의 워닝 메세지도 모두 없애는 습관을 들이자.