|
여기서 CPU의 자세한 내부 구조를 설명과 어셈블리와 컴퓨터 구조등의 low level 에 대한 이야기는 하지 않겠습니다. 왜냐하면 하다 보면 자연히 알게 될것이고 인라인어셈블러라는 목표에 맞는 내용만 담기 위해서입니다 (커플군의 실력,능력부족도 하나의 이유입니다 :D) 간단하게만 얘기해 보면, CPU는 어떠한 처리를 할때, 기본적인 방식은 1. RAM에 있는 데이타를 CPU 안에 복사 2. 이를 처리(연산) 3. 결과를 다시 RAM에 저장. 이러한 과정을 거치게 됩니다. 따라서, CPU 내부에는 RAM의 데이타를 읽어 와서 임시로 저장할 공간이 있는데 이 공간을 Register(레지스터) 라고 합니다. 이 레지스터라의 크기는 32bit 즉 integer 값 하나정도를 저장할 공간 밖에 되지않습니다. 불행중다행인지 이러한 Register가 하나로 부족해 몇개가 있습니다 이들에게 이름을 붙였는데. EAX, EBX, ECX, EDX.. 등과 같은 방법으로 붙는다. (절때 아무의미없이 ABCD 순이 아니다) 레지스터의 종류나 그 역활에 대해서는 차차 알아 보자. 나중에시간이되면 이에 대한글도 포스트하겠지만 보다 자세한 내용을 알고 싶은 분들은 어셈관련 참고서적을 참고하기 바랍니다 :) 이 번에는 소스를 한 번 살펴 봅시다 void memset ( void* dest, char fill, int num )
{ if (num <= 0) return; _asm { mov al, fill mov ecx, num mov edi, dest rep stosb } } 일단 얼핏 보기에도 C 코드와 어셈 코드가 섞여 있다. 요약 어셈 명령어
MOV 이 명령어는 데이타를 옮길때 사용한다, 1. 레지스터에 특정 값을 넣거나, 2. 레지스터에서 레지스터로 값을 옮기거나, 3. 메모리(RAM)에 있는 값을 레지스터로 가져 오거나, 4. 레지스터에서 메모리(RAM)로 데이타를 옮길때.... 애석하게도 MOV는 메모리(RAM)에서 메모리(RAM)으로 바로 데이타를 옮기지는 못한다. ex) MOV al, 10 // al 레지스터에 10이라는 값을 넣어라. MOV ebx, eax // eax 레지스터의 값을 ecx 레지스터에 넣어라. MOV ecx, [edi] // edi 레지스터가 가리키는 주소(RAM)에 있는 값을 ecx로 가져 와라. MOV [edi], ecx // ecx 레지스터에 있는 값을 edi 레지스터가 가리키는 주소(RAM)로 옮겨라.
void memcpy ( void* dest, void* src, int num )
{ if (num <= 0) return; _asm { mov edi, dest mov esi, src mov ecx, num cld rep movsb } } 한 번 분석해 봅시다. mov edi, dest
라는 명령을 실행했다고 하면, void memcpy ( void* dest, void* src, int num )
{ if (num <= 0) return; _asm { mov edi, dest mov eax, edi mov esi, src mov ecx, num mov edx, ecx shr ecx, 1 shr ecx, 1 cld rep movsd mov ecx, edx and ecx, 0x03 rep movsb } }
void strcpy (char* dest, char* src)
{ _asm { mov edi, src mov ecx, 0xffffffff xor al, al cld repnz scasb not ecx ... } } 자.. 이제 거의 다 아는 것들이 눈에 보이기 시작한다. 0101001
xor 1011000 ------------- 1110001 이해가 되시나요? 근대..xor al, al 왜 자신을 xor 했을까요? 1. 어떤 명령이....실행결과로써 플래그 레지스터를 건든다. (값을 바꾼다) 2. 뒤에서 이 플래그의 조건 (플래그의 값)에 따라 분기를 하거나 반복을 한다. 의 순을 따르게 됩니다. 위 소스를 정리하면, ecx에 0xffffffff를 넣고, al에 NULL을 넣고, NULL을 찿을때까지 반복해서 ecx값을 빼 나갑니다 NULL을 찿고 난뒤 ecx 값을 not 연산을 합니다 그러면, ecx에 src의 문자열에서 NULL까지의 문자수가 나오게됩니다 :) ecx값을 return 하면 바로 strlen 함수가 된는것이지요! 그리구 부분에 memcpy 기능을 붙여 넣으면 (ecx 만큼 memcpy를 하면) 바로 strcpy 함수가 완성되는 것이다. ecx에 0xffffffff 값을 넣고 빼 나가다가 이를 not 연산하면 원하는 카운트를 얻는 코드는 자주쓰이니 잘알아두자! strcpy 의 나머지 부분은 여러분들이 완성해보세요 :D 이번에는 ZeroMemory 함수를 작성해봅시다 이 함수 굳이 설명을 하지 않아도 memset과 자주 쓰이는 0으로의 초기화 함수중에 하나 입니다 :D 이것을 이번에 인라인 어셈으로 바꾸어 보도록합시다! push ebp mov esp,ebp .. .. pop ebp ret 그리고 여기에 쓰이는 최소한의 스택 2개들 그리고 수를 알수 없는 cmp 명령어의 사용 위의 코드는 함수의 전형적인 코드가 될것입니다. 이것들을 없에고 좀더 간단하게 코드를 줄여 보자!! 입니다. 아! 루프안이라면 아무래도 함수 보단 매크로 쪽이 좋을 듯 합니다.. (왜 인지는 한번 생각해 보심이 제 생각은 밑에 적겠습니다.) 우선 4의 배수의 크기를 가진 메모리를 초기화 하도록 합시다 타입을 2가지로 나누어야 합니다. 우선 포인터인것과 일반 적인 변수 형태 입니다. 코드 상에는 크게 차이는 없습니다. 그럼 적어 보도록 합니다 크기가 4의 배수 라면 xor eax,eax mov ecx,크기 shr ecx,2 mov edi,주소 or lea edi,변수 rep stosd 이것 입니다. 코드를 설명 하면 xor eax,eax // eax 를 0 으로 set해줍니다. 아까도 이야기했었죠? :) mov ecx,크기 이곳에는 크기를 넣어 주시면 됩니다. 직접 수를 적어 주셔도 되고 변수로도 가능합니다. 만약 변수의 이름이 length 라면 mov ecx,length 이런 식으로 해주셔도 됩니다. shr ecx,2 이것은 4로 나누어 주기 위한 코드 입니다. 오른쪽으로 2번 쉬프트는 4로 나눈것과 같다는건 다들아시죠? :) mov edi,주소 or lea edi,변수 edi 에는 변수의 주소가 꼭 들어 가야 합니다. 즉 0으로 채워질 메모리 주소 입니다. mov는 뭔지 아시리라 보고 설명을 하지 않겠습니다, lea는 그 변수의 주소를 넣어 줍니다. 만약 메모리 변수 이름이 buff 라면(포인터 변수는 아닙니다.) lea edi,buff 로 해주시면 됩니다. 변수 타입에 따라 2개 중에 하나를 쓰시기 바랍니다. rep stosd 가장 중요한 코드 인데 다시한번 자세히 설명해보도록합시다 굳이 풀자면 rep(eat) sto(re)s(tring)d(word)입니다. 반복해서(repeat) 저장해라(store) 문자열을(string) Dword로(Dword) 주 요는 edi에 eax를 ecx 만큼 저장 하라는 말입니다. Dword는 4바이트 입니다. 그러니까 eax전체가 edi의 주소로 복사가 됩니다.(eax는 4바이트이기 때문입니다.) 좀 부실 한것 같아서 재차 설명을 드리자면 eax는 4바이트 입니다 그런데 지금 위에서는 4바이트로 저장을 하죠? 그러니까 eax전체가 저장이 되는 것입니다. 만약 eax가 0x00FF00FF라면 그대로 0x00FF00FF가 저장이 되는 것입니다. xor eax,eax mov ecx,크기 mov edi,주소 or lea edi,변수 이부분은 rep stosd 이 부분을 실행하기 위해 초기화 해준다고 생각 하시면 될 듯 합니다 자 이제는 나머지 4의 배수가 아닌 부분을 생각 해보죠. 이것도 보시면 아하 하실 정도로 간단 합니다. mov eax,0 mov ecx,크기 mov edx,ecx shr ecx,2 mov edi,주소 or lea edi,변수 rep stosd mov ecx,edx and ecx,03 rep stosb 입니다. 약간의 차이는 있지만 거의 비슷 하죠? 우선 추가된 코드를 보죠 mov edx,ecx 임시적으로 크기를 저장한것입니다. mov ecx,edx and ecx,03 이것은 4로 나눈 나머지를 구하는 것입니다. 어떻게 되는지는 생각 해보세요 :D(이런 무책임한!!) rep stosb rep(eat) sto(re)s(tring)b(yte)입니다. 대충 무슨 말인지 알겠죠? 부실하지만 여기까지 입니다 내친김에 이것을 매크로로 만들어 쓰시면 괜찮을듯 합니다 :D 이제 조건문에 대해서 한 번 알아 봅시다. for (int i=0; i<10; i++) { ... } 우리가 C 에서 보는 가장 흔한 문장 중에 하나입니다. for 문을 살펴보면, 1. 초기값 설정 ( int i=0; ) 2. 조건부 ( i < 10; ) 3. 반복부분 ( i++; ) 이렇게 나눌 수 있다! 먼저, 초기값 설정부문 (int i=0;) 은 mov eax, i xor eax, eax 로 바꾸면 될 듯 하네요. 다음 조건부 ( i < 10; ) 복잡할 것 같으니 나중에 보도록합시다 반복부분 (i++;) 은 inc i 대충 합한 코드를 보면서 조건부분을 살펴보도록하자. mov eax, i // 초기값 세팅 부분 (i=0;) xor eax,eax // mov i,eax // loop_part: <-- 이게 뭔지는 알겠지? (위치를 표시하는 라벨) ... ... ... inc i // 반복부분 (i++;) mov edx,i // 조건부분 (i<10;) cmp edx, 0x0a // jl loop_part // cmp 라는 명령어가 보이네요. cmp = Compare (비교하라) cmp edx, 10 == (edx - 10) 연산을 해서 플레그 값들을 세팅하게되므로 (edx-10)이 0보다 작으면 SF(Sign Flag,부호플래그)=1 이 될 것입니다. jl = Jump Less (작으면 분기하라) cmp edx, 0x0a jl loop_part == edx 가 0x0a 보다 작으면 loop_part로 이동하라. 결국 loop_part 부분을 반복하게 될것입니다. 여기서 조건관련 수식어들을 살펴 보도록하자. JE = Jump Equal ( 같으면 분기하라) JNE = Jump Not Equal (같지 않으면 ...) JL = Less ( 작으면 ... ) JNL = Not Less JG = Greater ( 크면 ... ) JS = Sign ( (-)마이너스 이면 ... ) JZ = Zero ( 0 이면... ) 간단한 영어약자만 눈익힌다면 별거 아니다. (^_^) while 문도 한 번 살펴 보자 While 문과 For 문과 비교해 보면 초기값설정부와 반복부분이 없으니 오히려 더 쉽지않을까? 그 외는 아마도 동일할 것이다. while (i>0) { ... ... } 이를 코드로 바꾸면... loop_part: (라벨) ... ... mov eax, i test eax,eax jnle loop_part 별루 설명할 것도 없겠지만 test = Logical Compare test eax,eax 는 eax 와 eax를 논리적 AND 연산하여 그 결과로 플래그들을 세팅합니다. 여기서 하나 재밋는문제를 내보겠다 :D jnle 와 jg 는 무엇이 다를까? 즉 "작지도않고 같지도 않을때(초과)일때 점프하라" 와 "클때 점프하라" 의 차이점은 무엇이냐는것이다. 답은 둘다 같은명령어입니다. 이와 비슷한 명령어들이 있습니다 JG = JNLE JLE = JNG JNL = JGE JNGE = JL JPO = JNP JP = JPE JNBE = JA JNA = JBE JNZ = JNE JE = JZ // 비교하는 대상들의 차가 0이면 같은거겠죠? JAE = JNB JNAE = JB 는 서로 같습니다 앞에서는 if 문과 while문을 살펴 보았습니다 이번에는 switch 문을 한 번 살펴 보도록합시다 c 로 된 아래와 같은 코드가 있다고 생각해 보자! switch (x) { case 0: printf ("강태공 0"); break; case 2: printf ("강태공 2"); break; case 3: printf ("강태공 3"); break; case 8: printf ("강태공 8"); break; default: printf ("강태공 default"); break; }; 이를 어셈으로 바꾸면 아래와 같이 됩니다. mov edx, x sub edx, 0x01 jb case_0 dec edx jz case_2 dec edx jz case_3 sub edx,0x05 jz case_8 jmp case_default case_0: (라벨) ... case_2: (라벨) ... case_3: (라벨) ... case_8: (라벨) ... case_default: (라벨) ... 이제 구지 설명하지 않아도 잘 알것이라 믿습니다 :D switch 문이라서 별다를것 같지만, 막상 코드를 보면 별 다른것이 없습니다. 실망하셨나요? :( if/while/switch 문을 어셈으로 표현해 보면서 반대로 C 에서의 이들 구문의 특성을 알 수 있었습니다. 요즈음과 같이 펜티엄 4가 대세인 시대에 이들 구문들을 속도때문에 구분해서 사용한다는 것은 별 의미가 없을 수 있습니다 하지만, 혹시라도 속도를 고려해야할경우나, 좀더 손이더많이가는 구현이 필요한경우, 운영체제같은 프로그램을 작성할때라면 우리가 지금까지 한 이야기들은 절때 헛되지 않을것입니다 :) 마지막으로.. 이 쯤에서, 정리할 겸해서 CPU 명령들을 한 번 전체적으로 살펴 봐야할 것 같습니다 간단히 눈으로도 알아맞출수있는 명령어들이 많습니다. 명령어들만 나열할테니 자세한 것들을 알아내는건 여러분들에게 맏기도록하겠습니다 :D 1. 데이타 전송 명령들
CMOVE/CMOVZ CMOVNE/CMOVNZ CMOVA/CMOVNBE CMOVAE/CMOVNB CMOVB/CMOVNAE CMOVBE/CMOVNA CMOVG/CMOVNLE CMOVGE/CMOVNL CMOVL/CMOVNGE CMOVLE/CMOVNG CMOVC CMOVNC CMOVO CMOVNO CMOVS CMOVNS CMOVP/CMOVPE CMOVNP/CMOVPO XCHG BSWAP XADD CMPXCHG CMPXCHG8B PUSH POP PUSHA/PUSHAD POPA/POPAD IN OUT CWD/CDQ CBW/CWDE MOVSX MOVZX
|
|
댓글을 달아 주세요
강태공 naming sense가 멋지십니다. ㅎㅎ
TTF님이 이런곳까지 행차를 *( __)~
왔다가 그냥 티티에프가 보여서 흔적이라도... 커플이 화이팅...
열심히해야되는데말이죠..ㅠㅠ 감사합니다
Intel64 Architecture에선 R?X 계열 레지스터가 나오죠. 64bit 크기입니다.
Really ? eXtended 의 줄임말이라고 구글님이 말씀해주셨습니다.
32bit E?X 는 Extended ? eXtended
16bit ?X는 eXtended
8bit ?L ?H High와 Low
감사합니다!
앞으로 포스트 달때 관련내용이 나오면 참고해서 쓰도록하겠습니당!^_^
아는척좀 해봤습니다. ㅋㅋㅋ
그리고 Float 형 자료를 위한 레지스터도 나왔더군요... 모두 인텔홈피에서 매뉴얼을 다운로드 받아봅시다 +_+
택배신청하면 인쇄된 책을 무료로 주더군요. 배타고 건너오더군요. 전 두박스나 받았습니다. 하나 남았음
http://www.intel.com/products/processor/manuals/index.htm
지금 바로 링크를 타고 가세요
한글로 봐도 잘모르는데 영어라니 ㅠ_ㅠ..
좀더 공부한뒤에 보도록할께요~ 감사합니다~