[[TableOfContents]] = before = ì›ë¬¸ 출처 : http://blog.naver.com/webman21/18265245 ë‚´ìš©ì´ ë³¼ë§Œí•´ì„œ 올림. í .. ã…¡.ã…¡ ì´ì œ copy paste ë„ ê·€ì°®êµ¬ë‚˜;; í‚¤ë³´ë“œì— ì¹˜ê¸°ê°€ 싫다. = 개요 = 마ì´í¬ë¡œì»´í“¨í„° ì‹œìŠ¤í…œì˜ êµ¬ì„±ìš”ì†Œê°€ 무엇ì¸ê°€? 마ì´í¬ë¡œì»´í“¨í„° ì‹œìŠ¤í…œì€ ë§ˆì´í¬ë¡œí”„로세서 장치 (microprocessor unit, MPU), 버스 시스템, 메모리 하위시스템, ìž…ì¶œë ¥ 하위시스템, ëª¨ë“ êµ¬ì„±ìš”ì†Œë“¤ê°„ì˜ ì¸í„°íŽ˜ì´ìŠ¤ë¡œ 구성ëœë‹¤. ì „í˜•ì ì¸ ëŒ€ë‹µì´ë‹¤. ì´ëŠ” í•˜ë“œì›¨ì–´ë§Œì„ ê³ ë ¤í•œ 것ì´ë‹¤. ëª¨ë“ ë§ˆì´í¬ë¡œì»´í“¨í„° ì‹œìŠ¤í…œì€ í•˜ë“œì›¨ì–´ êµ¬ì„±ìš”ì†Œë“¤ì˜ ìž‘ì—…ì„ ì§€ì‹œí• ì†Œí”„íŠ¸ì›¨ì–´ê°€ 필요하다. 컴퓨터 소프트웨어는 시스템측(시스템 소프트웨어)ê³¼ 사용ìžì¸¡(ì‚¬ìš©ìž ì†Œí”„íŠ¸ì›¨ì–´)으로 êµ¬ë¶„í• ìˆ˜ 있다. í”„ë¡œê·¸ëž¨ì„ ì‹¤í–‰í•˜ê¸°ìœ„í•´ 필요한 í•¨ìˆ˜ë“¤ì„ ëª¨ì•„ë‘” 기본 ë¼ì´ë¸ŒëŸ¬ë¦¬ë‚˜ 사용ìžê°€ ë§Œë“ ë¼ì´ë¸ŒëŸ¬ë¦¬ëŠ” ì‚¬ìš©ìž ì†Œí”„íŠ¸ì›¨ì–´ì— í¬í•¨ëœë‹¤. ê³ ê¸‰ì–¸ì–´ 변환기, 어셈블러, 편집기, 다른 í”„ë¡œê·¸ëž¨ì„ ë§Œë“œëŠ” ìž‘ì—…ì„ ë•는 í”„ë¡œê·¸ëž¨ë“¤ì´ ì‹œìŠ¤í…œ ì†Œí”„íŠ¸ì›¨ì–´ì— ì†í•œë‹¤. 우리는 ì´ë¯¸ 프로그래ë°ì—는 기계어, 어셈블리어, ê³ ê¸‰ì–¸ì–´ 세 단계가 있ìŒì„ 안다. 기계어 í”„ë¡œê·¸ëž¨ì€ ì»´í“¨í„°ê°€ ì´í•´í•˜ê³ ì§ì ‘ ì‹¤í–‰í• ìˆ˜ 있는 프로그램ì´ë‹¤. 어셈블리어 ëª…ë ¹ì–´ëŠ” 기계어 ëª…ë ¹ì–´ì™€ 보통 ì¼ëŒ€ì¼ 관계로 대ì‘하지만, 우리가 쉽게 ì´í•´í• 수 있는 문ìžì—´ì„ 사용한다. ê³ ê¸‰ì–¸ì–´ ëª…ë ¹ì–´ëŠ” ì˜ì–´ì— 매우 가까워서 프로그래머가 ìƒê°í•˜ëŠ” ë°©ì‹ê³¼ ìžì—°ìŠ¤ëŸ½ê²Œ 대ì‘한다. ê²°êµ ì–´ì…ˆë¸”ë¦¬ì–´ë‚˜ ê³ ê¸‰ì–¸ì–´ í”„ë¡œê·¸ëž¨ì€ ë³€í™˜ê¸°ë¼ëŠ” í”„ë¡œê·¸ëž¨ì— ì˜í•´ 기계어로 변환ë˜ì•¼ 한다. ì´ ë³€í™˜ê¸°ë¥¼ ê°ê° 어셈블러(assembler), 컴파ì¼ëŸ¬(compiler) í˜¹ì€ ì¸í„°í”„리터(interpreter)ë¼ê³ 한다. C/C++ê°™ì€ ê³ ê¸‰ì–¸ì–´ì˜ ì»´íŒŒì¼ëŸ¬ëŠ” ê³ ê¸‰ì–¸ì–´ë¥¼ 어셈블리코드로 ë³€í™˜í• ìˆ˜ 있다. GNU C/C++ 컴파ì¼ëŸ¬ì˜ -S ì˜µì…˜ì€ í”„ë¡œê·¸ëž¨ ì†ŒìŠ¤ì— í•´ë‹¹í•˜ëŠ” 어셈블리코드를 ìƒì„±í•œë‹¤. 반복, 함수 호출, 변수 ì„ ì–¸ê³¼ ê°™ì€ ê¸°ë³¸ì ì¸ êµ¬ì¡°ê°€ 어셈블리어로 어떻게 대ì‘하는지 알면 C 내부를 ì´í•´í•˜ê¸° 쉽다. ì´ ê¸€ì„ ì´í•´í•˜ê¸°ìœ„해서는 컴퓨터구조와 Intel x86 ì–´ì…ˆë¸”ë¦¬ì–´ì— ìµìˆ™í•´ì•¼ 한다. = 시작 = ë¨¼ì € hello world를 ì¶œë ¥í•˜ëŠ” 간단한 C í”„ë¡œê·¸ëž¨ì„ ìž‘ì„±í•˜ê³ , -S 옵션으로 컴파ì¼í•œë‹¤. ìž…ë ¥íŒŒì¼ì— 대한 어셈블리코드를 ì–»ì„ ìˆ˜ 있다. GCC는 기본ì 으로 í™•ìž¥ìž `.c'를 `.s'로 변경하여 어셈블러파ì¼ëª…ì„ ì§“ëŠ”ë‹¤. ì–´ì…ˆë¸”ëŸ¬íŒŒì¼ ëì˜ ëª‡ì¤„ì„ í•´ì„í•´ë³´ìž. 80386 ì´ìƒ 프로세서ì—는 ë§Žì€ ë ˆì§€ìŠ¤í„°ì™€ ëª…ë ¹ì–´, ì£¼ì†Œì§€ì •ë°©ë²•ì´ ìžˆë‹¤. 그러나 간단한 ëª…ë ¹ì–´ 몇개만 좀 ì•Œì•„ë„ GNU 컴파ì¼ëŸ¬ê°€ 만드는 코드를 충분히 ì´í•´í• 수 있다. ì¼ë°˜ì 으로 어셈블리어 ëª…ë ¹ì–´ëŠ” ë¼ë²¨(label), ì—°ìƒê¸°í˜¸(mnemonic), 연산수(operand)로 구성ëœë‹¤. 연산수 표시방법ì—서 ì—°ì‚°ìˆ˜ì˜ ì£¼ì†Œì§€ì •ë°©ì‹ì„ 알 수 있다. ì—°ìƒê¸°í˜¸ëŠ” ì—°ì‚°ìˆ˜ì— ì €ìž¥ëœ ì •ë³´ì— ìž‘ì—…ì„ í•œë‹¤. 사실 어셈블리어 ëª…ë ¹ì–´ëŠ” ë ˆì§€ìŠ¤í„°ì™€ ë©”ëª¨ë¦¬ìœ„ì¹˜ì— ìž‘ì—…ì„ í•œë‹¤. 80386ê³„ì—´ì€ eax, ebx, ecx ë“±ì˜ (32비트) ë²”ìš©ë ˆì§€ìŠ¤í„°ë¥¼ 가진다. ë‘ ë ˆì§€ìŠ¤í„°, ebp와 esp는 스íƒì„ ì¡°ìž‘í• ë•Œ 사용한다. GNU Assembler (GAS) 문법으로 작성한 ì „í˜•ì ì¸ ëª…ë ¹ì–´ëŠ” 다ìŒê³¼ 같다: {{{~cpp movl $10, %eax }}} ì´ ëª…ë ¹ì–´ëŠ” eax ë ˆì§€ìŠ¤í„°ì— ê°’ 10ì„ ì €ìž¥í•œë‹¤. ë ˆì§€ìŠ¤í„°ëª… ì•žì˜ `%'와 ì§ì ‘ê°’(immediate value) ì•žì˜ '$'는 필수 어셈블러 문법ì´ë‹¤. ëª¨ë“ ì–´ì…ˆë¸”ëŸ¬ê°€ ì´ëŸ° ë¬¸ë²•ì„ ë”°ë¥´ëŠ” ê²ƒì€ ì•„ë‹ˆë‹¤. ëª©ë¡ 1ì€ first.s 파ì¼ì— ì €ìž¥í•œ ìš°ë¦¬ì˜ ì²«ë²ˆì§¸ 어셈블리어 프로그램ì´ë‹¤. #ëª©ë¡ 1 {{{~cpp .globl main main: movl $20, %eax ret }}} cc first.s ëª…ë ¹ì–´ë¥¼ 실행하면 ì´ íŒŒì¼ì„ ì–´ì…ˆë¸”í•˜ê³ ë§í¬í•˜ì—¬ a.outì„ ë§Œë“ ë‹¤. GNU 컴파ì¼ëŸ¬ 앞단 ccê°€ `.s' 확장ìžë¥¼ 어셈블리어 파ì¼ë¡œ ì¸ì‹í•˜ì—¬, 컴파ì¼ë‹¨ê³„를 ìƒëžµí•˜ê³ 어셈블러와 ë§ì»¤ë¥¼ 부른다. í”„ë¡œê·¸ëž¨ì˜ ì²«ë²ˆì§¸ ì¤„ì€ ì£¼ì„ì´ë‹¤. 어셈블러 지시어 .globlì€ ì‹¬ë³¼ mainì„ ë§ì»¤ê°€ ë³¼ 수 있ë„ë¡ ë§Œë“ ë‹¤. 그래야 mainì„ í˜¸ì¶œí•˜ëŠ” C 시작ë¼ì´ë¸ŒëŸ¬ë¦¬ë¥¼ 프로그램과 ê°™ì´ ë§í¬í•˜ë¯€ë¡œ 중요하다. ì´ ì¤„ì´ ì—†ë‹¤ë©´ ë§ì»¤ëŠ” 'undefined reference to symbol main' (심볼 mainì— ëŒ€í•œ 참조가 ì •ì˜ë˜ì§€ì•ŠìŒ)ì„ ì¶œë ¥í•œë‹¤ (한번 í•´ë´ë¼). í”„ë¡œê·¸ëž¨ì€ ë‹¨ìˆœížˆ ë ˆì§€ìŠ¤í„° eaxì— ê°’ 20ì„ ì €ìž¥í•˜ê³ í˜¸ì¶œìžì—게 반환한다. = ì‚°ìˆ ê³„ì‚°, 비êµ, 반복 = ë‹¤ìŒ ëª©ë¡ 2 í”„ë¡œê·¸ëž¨ì€ eaxì— ì €ìž¥ëœ ê°’ì˜ ê³„ìŠ¹(factorial)ì„ ê³„ì‚°í•œë‹¤. 결과를 ebxì— ì €ìž¥í•œë‹¤. #ëª©ë¡ 2 {{{~cpp .globl main main: movl $5, %eax movl $1, %ebx L1: cmpl $0, %eax //eaxì— ì €ìž¥ëœ ê°’ê³¼ 0ì„ ë¹„êµ je L2 //0==eax ì´ë©´ L2로 건너뜀 (je - jump if equal) imull %eax, %ebx // ebx = ebx*eax decl %eax //eax ê°ì†Œ jmp L1 // L1으로 무조건 건너뜀 L2: ret }}} L1ê³¼ L2는 ë¼ë²¨ì´ë‹¤. ì œì–´íë¦„ì´ L2ì— ë„달하면, ebx는 eaxì— ì €ìž¥ëœ ê°’ì˜ ê³„ìŠ¹ì„ ì €ìž¥í•˜ê²Œ ëœë‹¤. = 함수(subroutine) = 복잡한 í”„ë¡œê·¸ëž¨ì„ ë§Œë“¤ë•Œ 우리는 í•´ê²°í• ë¬¸ì œë¥¼ 체계ì 으로 나눈다. ê·¸ë¦¬ê³ í•„ìš”í• ë•Œë§ˆë‹¤ í˜¸ì¶œí• í•¨ìˆ˜ë¥¼ 작성한다. ëª©ë¡ 3ì€ ì–´ì…ˆë¸”ë¦¬ì–´ í”„ë¡œê·¸ëž¨ì˜ í•¨ìˆ˜ 호출과 ë°˜í™˜ì„ ë³´ì—¬ì¤€ë‹¤. #ëª©ë¡ 3 {{{~cpp .globl main main: movl $10, %eax call foo ret foo: addl $5, %eax ret }}} call ëª…ë ¹ì–´ëŠ” ì‹¤í–‰ì„ í•¨ìˆ˜ foo로 옮긴다. fooì˜ ret ëª…ë ¹ì–´ëŠ” ì‹¤í–‰ì„ ë‹¤ì‹œ mainì˜ í˜¸ì¶œ 다ìŒì— 나오는 ëª…ë ¹ì–´ë¡œ 옮긴다. ì¼ë°˜ì 으로 함수는 함수가 ì‚¬ìš©í• ë³€ìˆ˜ë“¤ì„ ì •ì˜í•œë‹¤. ì´ ë³€ìˆ˜ë“¤ì„ ìœ ì§€í•˜ë ¤ë©´ ê³µê°„ì´ í•„ìš”í•˜ë‹¤. 함수 호출시 ë³€ìˆ˜ê°’ì„ ìœ ì§€í•˜ê¸°ìœ„í•´ 스íƒì„ 사용한다. 프로그램 ì‹¤í–‰ì¤‘ì— ë°˜ë³µë˜ëŠ” 재귀호출시(recursive call) activation recordê°€ ìœ ì§€ë˜ëŠ” ë°©ë²•ì„ ì´í•´í•˜ëŠ” ê²ƒì´ ì¤‘ìš”í•˜ë‹¤. esp나 ebpê°™ì€ ë ˆì§€ìŠ¤í„° 사용법과 스íƒì„ 다루는 push와 popê°™ì€ ëª…ë ¹ì–´ ì‚¬ìš©ë²•ì€ í•¨ìˆ˜í˜¸ì¶œê³¼ 반환방ì‹ì„ ì´í•´í•˜ëŠ”ë° ì¤‘ìš”í•˜ë‹¤. = ìŠ¤íƒ ì‚¬ìš©í•˜ê¸° = í”„ë¡œê·¸ëž¨ì˜ ë©”ëª¨ë¦¬ ì¼ë¶€ë¥¼ 스íƒìœ¼ë¡œ 사용하기위해 비워ë‘었다. Intel 80386 ì´ìƒì˜ 마ì´í¬ë¡œí”„로세서ì—는 ìŠ¤íƒ ìµœìƒìœ„ 주소를 ì €ìž¥í•˜ëŠ”, 스íƒí¬ì¸í„°(stack pointer)ë¼ëŠ” esp ë ˆì§€ìŠ¤í„°ê°€ 있다. 아래 그림 1ì€ ìŠ¤íƒì— ì €ìž¥ëœ ì„¸ ì •ìˆ˜ê°’ 49, 30, 72를 보여준다 (ì •ìˆ˜ëŠ” ê°ê° 4 ë°”ì´íŠ¸ë¥¼ 차지한다). esp ë ˆì§€ìŠ¤í„°ëŠ” ìŠ¤íƒ ìµœìƒìœ„ 주소를 ì €ìž¥í•œë‹¤. 그림 1 위로 쌓여가는 ë²½ëŒê³¼ 달리 Intel ì»´í“¨í„°ì˜ ìŠ¤íƒì€ 아래방향으로 ìžëž€ë‹¤. 그림 2는 ëª…ë ¹ì–´ pushl $15를 실행한후 스íƒì„ 보여준다. 그림 2 스íƒí¬ì¸í„° ë ˆì§€ìŠ¤í„°ëŠ” 4ë§Œí¼ ê°ì†Œí•˜ê³ , ìˆ«ìž 15를 4 ë°”ì´íЏ(주소 1988, 1989, 1990, 1991)ì— ì €ìž¥í•œë‹¤. ëª…ë ¹ì–´ popl %eax는 ìŠ¤íƒ ìµœìƒìœ„ì— ìžˆëŠ” ê°’(4 ë°”ì´íЏ)ì„ eax ë ˆì§€ìŠ¤í„°ì— ë³µì‚¬í•˜ê³ esp를 4ë§Œí¼ ì¦ê°€í•œë‹¤. 만약 ìŠ¤íƒ ìµœìƒìœ„ì— ìžˆëŠ” ê°’ì„ ë ˆì§€ìŠ¤í„°ì— ë³µì‚¬í•˜ê³ ì‹¶ì§€ 않다면? ëª…ë ¹ì–´ addl $4, %esp를 실행하여 스íƒí¬ì¸í„°ë§Œ ì¦ê°€í•˜ë©´ ëœë‹¤. ëª©ë¡ 3ì—서 ëª…ë ¹ì–´ call foo는 í˜¸ì¶œì„ ë§ˆì¹œí›„ ì‹¤í–‰í• ëª…ë ¹ì–´ì˜ ì£¼ì†Œë¥¼ 스íƒì— ë„£ê³ foo로 분기한다. 함수는 retì—서 ëë‚˜ê³ , ì‹¤í–‰ì„ ìŠ¤íƒ ìµœìƒìœ„ì—서 ê°€ì ¸ì˜¨ ì£¼ì†Œì— ìžˆëŠ” ëª…ë ¹ì–´ë¡œ 옮긴다. ë¬¼ë¡ ìŠ¤íƒ ìµœìƒìœ„ì— ìœ íš¨í•œ 반환주소가 있어야 한다. = ì§€ì—변수(local variable) 공간 í• ë‹¹í•˜ê¸° = C í”„ë¡œê·¸ëž¨ì€ ìˆ˜ë°± ìˆ˜ì²œê°œì˜ ë³€ìˆ˜ë¥¼ 다룰 수 있다. C í”„ë¡œê·¸ëž¨ì— í•´ë‹¹í•˜ëŠ” 어셈블리코드는 어떻게 변수를 ì €ìž¥í•˜ë©° 변수를 다루기위해 ë ˆì§€ìŠ¤í„°ë¥¼ ì¶©ëŒì—†ì´ 사용하는지 ì•Œë ¤ì¤€ë‹¤. ë ˆì§€ìŠ¤í„° 개수가 ì ê¸°ë•Œë¬¸ì— í”„ë¡œê·¸ëž¨ì˜ ëª¨ë“ ë³€ìˆ˜ë¥¼ ë ˆì§€ìŠ¤í„°ì— ë‹´ì„ ìˆ˜ëŠ” 없다. ì§€ì—변수는 스íƒì— 위치한다. ëª©ë¡ 4ê°€ ê·¸ ë°©ë²•ì„ ë³´ì—¬ì¤€ë‹¤. #ëª©ë¡ 4 {{{~cpp .globl main main: call foo ret foo: pushl %ebp movl %esp, %ebp subl $4, %esp movl $10, -4(%ebp) movl %ebp, %esp popl %ebp ret }}} ë¨¼ì € 스íƒí¬ì¸í„°ì˜ ê°’ì„ ê¸°ì¤€í¬ì¸í„° ë ˆì§€ìŠ¤í„°(base pointer register) ebpì— ë³µì‚¬í•œë‹¤. 기준í¬ì¸í„°ëŠ” 스íƒì˜ 다른 위치를 ì ‘ê·¼í• ë•Œ ì‚¬ìš©í• ê³ ì •ëœ ê¸°ì¤€ì ì´ë‹¤. foo를 호출한 코드ì—ì„œë„ ebp를 사용하므로, ê°’ì„ esp 값으로 대체하기 ì „ì— ìŠ¤íƒì— 복사한다. ëª…ë ¹ì–´ subl $4, %esp는 스íƒí¬ì¸í„°ë¥¼ ê°ì†Œí•˜ì—¬ ì •ìˆ˜ë¥¼ 담기위한 (4 ë°”ì´íЏ) ê³µê°„ì„ ë§Œë“ ë‹¤. ë‹¤ìŒ ì¤„ì€ ê°’ 10ì„ ebpì—서 4를 뺀 (4 ë°”ì´íЏ) ì£¼ì†Œì— ë³µì‚¬í•œë‹¤. ëª…ë ¹ì–´ movl %ebp, %esp는 스íƒí¬ì¸í„°ë¥¼ foo 시작시 ê°€ì¡Œë˜ ê°’ìœ¼ë¡œ ë˜ëŒë¦¬ê³ , popl %ebp는 기준í¬ì¸í„° ë ˆì§€ìŠ¤í„°ì˜ ê°’ì„ ë˜ëŒë¦°ë‹¤. 스íƒí¬ì¸í„°ëŠ” ì´ì œ foo를 시작하기 ì „ê³¼ ê°™ì€ ê°’ì„ ê°€ì§„ë‹¤. 아래 표는 main 시작과 ëª©ë¡ 4ì˜ (mainì—서 ë°˜í™˜ì„ ì œì™¸í•œ) ê° ëª…ë ¹ì–´ 실행후 ë ˆì§€ìŠ¤í„° ebp, esp와 3988ì—서 3999까지 ìŠ¤íƒ ì£¼ì†Œì˜ ë‚´ìš©ì´ë‹¤. 우리는 mainì˜ ì²« ëª…ë ¹ì–´ ì‹¤í–‰ì „ì— ebp는 ê°’ 7000, esp는 ê°’ 4000ì„ ê°€ì§€ë©°, ìŠ¤íƒ ì£¼ì†Œ 3988ì—서 3999까지 ìž„ì˜ì˜ ê°’ 219986, 1265789, 86ì´ ì €ìž¥ë˜ìžˆë‹¤ê³ ê°€ì •í•œë‹¤. ë˜, mainì—서 call foo 다ìŒì— 나오는 ëª…ë ¹ì–´ì˜ ì£¼ì†Œê°€ 30000ì´ë¼ê³ ê°€ì •í•œë‹¤. 표 1 = 파ë¼ë¯¸í„° ì „ë‹¬ê³¼ ê°’ 반환 = 함수로 파ë¼ë¯¸í„°ë¥¼ ì „ë‹¬í•˜ê¸°ìœ„í•´ 스íƒì„ ì‚¬ìš©í• ìˆ˜ 있다. 우리는 함수가 eax ë ˆì§€ìŠ¤í„°ì— ì €ìž¥í•œ ê°’ì´ í•¨ìˆ˜ì˜ ë°˜í™˜ê°’ì´ë¼ëŠ” (우리가 사용하는 C 컴파ì¼ëŸ¬ì˜) ê·œì¹™ì„ ë”°ë¥¸ë‹¤. 함수를 호출하는 í”„ë¡œê·¸ëž¨ì€ ìŠ¤íƒì— ê°’ì„ ë„£ì–´ì„œ 함수ì—게 파ë¼ë¯¸í„°ë¥¼ ì „ë‹¬í•œë‹¤. ëª©ë¡ 5는 sqrì´ë¼ëŠ” 간단한 함수로 ì´ë¥¼ 설명한다. #ëª©ë¡ 5 {{{~cpp .globl main main: movl $12, %ebx pushl %ebx call sqr addl $4, %esp //esp를 push ì´ì „ 값으로 ì¡°ì • ret sqr: movl 4(%esp), %eax imull %eax, %eax //eax * eax를 계산하여, 결과를 eaxì— ì €ìž¥ ret }}} sqrì˜ ì²«ë²ˆì§¸ ì¤„ì„ ì£¼ì˜ìžˆê²Œ 살펴ë¼. 함수를 부르는 ì¸¡ì€ ebxì˜ ë‚´ìš©ì„ ìŠ¤íƒì— ë„£ê³ ëª…ë ¹ì–´ callì„ ì‹¤í–‰í•œë‹¤. 호출시 반환주소를 스íƒì— 넣는다. ê·¸ë¦¬ê³ sqr는 ìŠ¤íƒ ìµœìƒìœ„ì—서 4 ë°”ì´íЏ 떨어진 ê³³ì—서 파ë¼ë¯¸í„°ë¥¼ ì½ì„ 수 있다. = C와 어셈블러 섞기 = ëª©ë¡ 6ì€ C 프로그램과 어셈블리어 함수를 보여준다. íŒŒì¼ main.cì— C 함수가 ìžˆê³ sqr.sì— ì–´ì…ˆë¸”ë¦¬ì–´ 함수가 있다. cc main.c sqr.s를 ìž…ë ¥í•˜ì—¬ 파ì¼ë“¤ì„ 컴파ì¼í•˜ê³ ê°™ì´ ë§í¬í•œë‹¤. ë°˜ëŒ€ë„ ë§¤ìš° 간단하다. ëª©ë¡ 7ì€ C 함수 print와 ì´ í•¨ìˆ˜ë¥¼ 호출하는 어셈블리어를 보여준다. #ëª©ë¡ 6 {{{~cpp //main.c main() { int i = sqr(11); printf("%d\n",i); } //sqr.s .globl sqr sqr: movl 4(%esp), %eax imull %eax, %eax ret #ëª©ë¡ 7 //print.c print(int i) { printf("%d\n",i); } //main.s .globl main main: movl $123, %eax pushl %eax call print addl $4, %esp ret }}} = GNU Cê°€ 만드는 어셈블러 ì¶œë ¥ = 나는 ì´ ê¸€ì´ gccê°€ 만드는 어셈블러 ì¶œë ¥ì„ ì´í•´í•˜ê¸°ì— 충분하길 기대한다. ëª©ë¡ 8ì€ gcc -S add.c로 ë§Œë“ íŒŒì¼ add.s를 보여준다. add.s를 편집하여 ë§Žì€ (대부분 ì •ë ¬(alignment) ë“±ì˜ ëª©ì ì˜) 어셈블러 지서어를 ì‚ì œí•˜ì˜€ìŒì„ ë°ížŒë‹¤. #ëª©ë¡ 8 {{{~cpp //add.c int add(int i,int j) { int p = i + j; return p; } //add.s .globl add add: pushl %ebp movl %esp, %ebp subl $4, %esp //ì •ìˆ˜ pì˜ ê³µê°„ ìƒì„± movl 8(%ebp),%edx //8(%ebp)는 i를 ì§€ì¹ addl 12(%ebp), %edx //12(%ebp)는 j를 ì§€ì¹ movl %edx, -4(%ebp) //-4(%ebp)는 p를 ì§€ì¹ movl -4(%ebp), %eax //ë°˜í™˜ê°’ì„ eaxì— ì €ìž¥ leave //즉, movl %ebp, %esp; popl %ebp ret }}} ì´ í”„ë¡œê·¸ëž¨ì€ C 문장 add(10,20)ì´ ë‹¤ìŒê³¼ ê°™ì€ ì–´ì…ˆë¸”ëŸ¬ì½”ë“œë¡œ 변환ë¨ì„ 확ì¸í•˜ë©´ 명확해진다: {{{~cpp pushl $20 pushl $10 call add }}} ë‘번째 파ë¼ë¯¸í„°ë¥¼ ë¨¼ì € 넣는 ê²ƒì„ ì£¼ëª©í•˜ë¼. 10. ì „ì—변수(global variable) ì§€ì—ë³€ìˆ˜ì˜ ê³µê°„ì€ ìŠ¤í…í¬ì¸í„°ë¥¼ ê°ì†Œí•˜ì—¬ 스íƒì— í™•ë³´í•˜ê³ , 단순히 스íƒí¬ì¸í„°ë¥¼ ëŠ˜ë ¤ì„œ í• ë‹¹ëœ ê³µê°„ì„ ë˜ëŒë¦°ë‹¤. 그러면 GNU Cê°€ ì „ì—ë³€ìˆ˜ì— ëŒ€í•´ì„œëŠ” ì–´ë–¤ 코드를 ìƒì„±í• 까? ëª©ë¡ 9ê°€ í•´ë‹µì„ ì¤€ë‹¤. #ëª©ë¡ 9 {{{~cpp //glob.c int foo = 10; main() { int p = foo; } //glob.s .globl foo foo: .long 10 .globl main main: pushl %ebp movl %esp,%ebp subl $4,%esp movl foo,%eax movl %eax,-4(%ebp) leave ret }}} 문장 foo: .long 10ì€ fooë¼ëŠ” 4 ë°”ì´íЏ ë©ì–´ë¦¬ë¥¼ ì •ì˜í•˜ê³ , ì´ ë©ì–´ë¦¬ë¥¼ 10으로 초기화한다. 지시어 .globl foo는 다른 파ì¼ì—ì„œë„ foo를 ì ‘ê·¼í• ìˆ˜ 있ë„ë¡ í•œë‹¤. ì´ì œ ì´ê²ƒì„ 살펴보ìž. 문장 int foo를 static int foo로 ìˆ˜ì •í•œë‹¤. 어셈블리코드가 어떻게 살펴ë´ë¼. 어셈블러 지시어 .globlì´ ë¹ ì§„ ê²ƒì„ í™•ì¸í• 수 있다. (double, long, short, const 등) 다른 storage classì— ëŒ€í•´ì„œë„ ì‹œë„í•´ë³´ë¼. = 시스템호출(system call) = í”„ë¡œê·¸ëž¨ì´ ì–´ì…ˆë¸”ë¦¬ë¡œ 수학 ì•Œê³ ë¦¬ì¦˜ë§Œì„ êµ¬í˜„í•˜ì§€ 않는다면, ìž…ë ¥ì„ ë°›ê³ , ì¶œë ¥í•˜ê³ , 종료하는 등 ì–´ë–¤ ìž‘ì—…ì´ í•„ìš”í•˜ë‹¤. ì´ë¥¼ 위해 ìš´ì˜ì²´ì œ 서비스를 호출해야 한다. 사실 ìš´ì˜ì²´ì œ 서비스를 ì œì™¸í•˜ê³ ëŠ” 여러 ìš´ì˜ì²´ì œê°„ì˜ ì–´ì…ˆë¸”ë¦¬ì–´ 프로그래ë°ì´ 매우 비슷하다. 리눅스ì—는 ì‹œìŠ¤í…œí˜¸ì¶œì„ í•˜ëŠ” ë‘가지 ì¼ë°˜ì ì¸ ë°©ë²•ì´ ìžˆë‹¤: C ë¼ì´ë¸ŒëŸ¬ë¦¬ (libc) wrapper를 통하거나, ì§ì ‘. Libc wrapper는 시스템호출 ê·œì¹™ì´ ë³€ê²½ë˜ëŠ” 경우 í”„ë¡œê·¸ëž¨ì„ ë³´í˜¸í•˜ê³ , 커ë„ì— ê·¸ëŸ° ì‹œìŠ¤í…œí˜¸ì¶œì´ ì—†ëŠ” 경우 POSIX 호환 ì¸í„°íŽ˜ì´ìŠ¤ë¥¼ ì œê³µí•˜ê¸°ìœ„í•´ 만들어졌다. 그러나, ìœ ë‹‰ìŠ¤ 커ë„ì€ ë³´í†µ ê±°ì˜ POSIXì— í˜¸í™˜í•œë‹¤: 즉 ëŒ€ë¶€ë¶„ì˜ libc "시스템콜"ì˜ ë¬¸ë²•ì€ ì‹¤ì œ ì»¤ë„ ì‹œìŠ¤í…œí˜¸ì¶œì˜ ë¬¸ë²•ê³¼ (반대로ë„) ì •í™•ížˆ ì¼ì¹˜í•œë‹¤. 그러나 libc를 버리지않는 ì´ìœ 는 시스템콜 wrapperì™¸ì— printf(), malloc() 등 í•¨ìˆ˜ë„ ìžˆê¸°ë•Œë¬¸ì´ë‹¤. 리눅스 ì‹œìŠ¤í…œí˜¸ì¶œì€ int 0x80ì„ í†µí•´ 한다. 리눅스는 ì¼ë°˜ì ì¸ ìœ ë‹‰ìŠ¤ 호출 규칙과 다른 "fastcall" ê·œì¹™ì„ ì‚¬ìš©í•œë‹¤. 시스템함수 번호는 eaxì—, 아규먼트는 스íƒì´ 아닌 ë ˆì§€ìŠ¤í„°ë¥¼ 통해 ì „ë‹¬í•œë‹¤. ë”°ë¼ì„œ ebx, ecx, edx, esi, edi, ebpì— ì•„ê·œë¨¼íŠ¸ 6개까지 가능하다. 아규먼트가 ë” ìžˆë‹¤ë©´ 간단히 구조체를 첫번째 아규먼트로 넘긴다. 결과는 eax로 ë°˜í™˜í•˜ê³ , 스íƒì„ ì „í˜€ 건드리지 않는다. 아래 ëª©ë¡ 10ì„ ì‚´íŽ´ë³´ìž. #ëª©ë¡ 10 {{{~cpp #fork.c #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> int main() { fork(); printf("Hello\n"); return 0; } }}} ëª…ë ¹ì–´ cc -g fork.c -static으로 í”„ë¡œê·¸ëž¨ì„ ì»´íŒŒì¼í•œë‹¤. gdb ë„구ì—서 ëª…ë ¹ì–´ disassemble fork를 ìž…ë ¥í•œë‹¤. forkì— í•´ë‹¹í•˜ëŠ” 어셈블리코드를 ë³¼ 수 있다. -staticì€ GCCì˜ ì •ì ë§ì»¤ 옵션ì´ë‹¤ (manpage ì°¸ê³ ). 다른 ì‹œìŠ¤í…œí˜¸ì¶œë„ í…ŒìŠ¤íŠ¸í•´ë³´ê³ ì‹¤ì œ 어떻게 함수가 ë™ìž‘하는지 살펴ë´ë¼. 리눅스 ì‹œìŠ¤í…œí˜¸ì¶œì— ëŒ€í•œ ìµœì‹ ë¬¸ì„œê°€ 많아서 ì—¬ê¸°ì— ë°˜ë³µí•˜ì§€ ì•Šê² ë‹¤. = ì¸ë¼ì¸ 어셈블리 í”„ë¡œê·¸ëž˜ë° = GNU C는 x86 아키í…ì³ë¥¼ 매우 잘 ì§€ì›í•˜ë©°, C í”„ë¡œê·¸ëž¨ì— ì–´ì…ˆë¸”ë¦¬ì½”ë“œë¥¼ ì‚½ìž…í• ìˆ˜ 있다. ë ˆì§€ìŠ¤í„° í• ë‹¹ì€ ì§ì ‘ 지시하거나 GCCì— ë§¡ê²¨ë‘˜ 수 있다. ë¬¼ë¡ , 어셈블리 ëª…ë ¹ì–´ëŠ” 아키í…ì³ë§ˆë‹¤ 다르다. asm ëª…ë ¹ì–´ë¥¼ 사용하여 어셈블리 ëª…ë ¹ì–´ë¥¼ C나 C++ í”„ë¡œê·¸ëž¨ì— ì‚½ìž…í• ìˆ˜ 있다. 예를 들어: {{{~cpp asm ("fsin" : "=t" (answer) : "0" (angle)); }}} 는 ë‹¤ìŒ C ë¬¸ìž¥ì„ x86 ì‹ìœ¼ë¡œ 코딩한 것ì´ë‹¤: {{{~cpp answer = sin(angle); }}} ì¼ë°˜ì ì¸ ì–´ì…ˆë¸”ë¦¬ì½”ë“œ ëª…ë ¹ì–´ì™€ 달리 asm ë¬¸ìž¥ì€ C 문법으로 ìž…ë ¥ê³¼ ì¶œë ¥ 연산수를 ì§€ì •í• ìˆ˜ 있다. Asm ë¬¸ìž¥ì€ ì•„ë¬´ë•Œë‚˜ 사용하면 안ëœë‹¤. 그러면 ì–¸ì œ 사용해야 하나? Asm ë¬¸ìž¥ì€ í”„ë¡œê·¸ëž¨ì´ ì»´í“¨í„° í•˜ë“œì›¨ì–´ì— ì§ì ‘ ì ‘ê·¼í•˜ê²Œ 한다. 그래서 빨리 실행ë˜ëŠ” í”„ë¡œê·¸ëž¨ì„ ë§Œë“¤ 수 있다. 하드웨어와 ì§ì ‘ ìƒí˜¸ìž‘용하는 ìš´ì˜ì²´ì œ 코드를 ìž‘ì„±í• ë•Œ ì‚¬ìš©í• ìˆ˜ 있다. 예를 들어, /usr/include/asm/io.hì—는 ìž…ì¶œë ¥ í¬íŠ¸ë¥¼ ì§ì ‘ ì ‘ê·¼í•˜ê¸°ìœ„í•œ 어셈블리 ëª…ë ¹ì–´ê°€ 있다. ë˜, ì¸ë¼ì¸ 어셈블리 ëª…ë ¹ì–´ëŠ” í”„ë¡œê·¸ëž¨ì˜ ê°€ìž¥ 안쪽 ë°˜ë³µë¬¸ì˜ ì†ë„를 ë¹ ë¥´ê²Œí•œë‹¤. 예를 들어, ì–´ë–¤ ê°™ì€ ê°ë„ì— ëŒ€í•œ sineê³¼ cosineì€ fsincos x86 ëª…ë ¹ì–´ë¡œ ì–»ì„ ìˆ˜ 있다. ì•„ë§ˆë„ ì•„ëž˜ ë‘ ëª©ë¡ì€ ì´ ì ì„ ìž˜ ì´í•´í•˜ë„ë¡ ë„와줄 것ì´ë‹¤. #ëª©ë¡ 11 #ì´ë¦„ : bit-pos-loop.c #설명 : ë°˜ë³µë¬¸ì„ ì‚¬ìš©í•˜ì—¬ 비트 위치 찾기 {{{~cpp #include <stdio.h> #include <stdlib.h> int main (int argc, char *argv[]) { long max = atoi (argv[1]); long number; long i; unsigned position; volatile unsigned result; for (number = 1; number <= max; ; ++number) { for (i=(number>>1), position=0; i!=0; ++position) i >>= 1; result = position; } return 0; } }}} #ëª©ë¡ 12 #ì´ë¦„ : bit-pos-asm.c #설명 : bsrlì„ ì‚¬ìš©í•˜ì—¬ 비트 위치 찾기 {{{~cpp #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { long max = atoi(argv[1]); long number; unsigned position; volatile unsigned result; for (number = 1; number <= max; ; ++number) { asm("bsrl %1, %0" : "=r" (position) : "r" (number)); result = position; } return 0; } }}} 다ìŒê³¼ ê°™ì´ ìµœìƒì˜ 최ì 화로 ë‘ ì½”ë“œë¥¼ 컴파ì¼í•œë‹¤: {{{~cpp $ cc -O2 -o bit-pos-loop bit-pos-loop.c $ cc -O2 -o bit-pos-asm bit-pos-asm.c }}} 최소한 몇 ì´ˆë™ì•ˆ 실행ë˜ë„ë¡ í° ê°’ì„ ëª…ë ¹í–‰ 아규먼트로 ì£¼ê³ time ëª…ë ¹ì–´ë¥¼ 사용하여 ë‘ ì½”ë“œì˜ ì‹¤í–‰ì‹œê°„ì„ ìž°ë‹¤. {{{~cpp $ time ./bit-pos-loop 250000000 }}} and {{{~cpp $ time ./bit-pos-asm 250000000 }}} 결과는 컴퓨터마다 다를 것ì´ë‹¤. 그러나 ì¸ë¼ì¸ 어셈블리를 사용한 코드가 매우 ë¹ ë¥´ê²Œ 실행ë¨ì„ 확ì¸í• 수 있다. GCCì˜ ìµœì 화는 asm í‘œí˜„ì´ ìžˆë”ë¼ë„ ì‹¤í–‰ì‹œê°„ì„ ìµœì†Œí™”í•˜ê¸°ìœ„í•´ 프로그램 코드를 ìž¬ë°°ì—´í•˜ê³ ìž¬ìž‘ì„±í•˜ë ¤ê³ ì‹œë„한다. asmì˜ ì¶œë ¥ê°’ì„ ì‚¬ìš©í•˜ì§€ ì•ŠëŠ”ë‹¤ê³ íŒë‹¨í•˜ë©´, asmê³¼ 아규먼트 사ì´ì— 키워드 volatileì´ ì—†ëŠ” 한 최ì 화는 ëª…ë ¹ì–´ë¥¼ ìƒëžµí•œë‹¤. (특별한 경우로 GCC는 ì¶œë ¥ 연산수가 없는 asmì„ ë°˜ë³µë¬¸ 밖으로 옮기지 않는다.) asmì€ ì˜ˆì¸¡í•˜ê¸° íž˜ë“ ë°©ì‹ìœ¼ë¡œ, 심지어 호출간ì—ë„, 옮겨질 수 있다. 특별한 어셈블리 ëª…ë ¹ì–´ 순서를 보장하는 ìœ ì¼í•œ ë°©ë²•ì€ ëª¨ë“ ëª…ë ¹ì–´ë¥¼ ëª¨ë‘ ê°™ì€ asmì— í¬í•¨í•˜ëŠ” 것ì´ë‹¤. 컴파ì¼ëŸ¬ê°€ asmì˜ ì˜ë¯¸ë¥¼ ëª¨ë¥´ê¸°ë•Œë¬¸ì— asmì„ ì‚¬ìš©í•˜ë©´ 컴파ì¼ëŸ¬ì˜ íš¨ìœ¨ì„±ì„ ì œí•œí• ìˆ˜ 있다. GCC는 ì–´ë–¤ 최ì 화를 ë§‰ì„ ìˆ˜ 있는 보수ì ì¸ ì¶”ì¸¡ì„ í•˜ê²Œ ëœë‹¤. = ì—°ìŠµë¬¸ì œ = ëª©ë¡ 6ì˜ C í”„ë¡œê·¸ëž¨ì— ëŒ€í•œ 어셈블리코드를 í•´ì„하ë¼. 어셈블리코드를 ìƒì„±í• 때 -Wall ì˜µì…˜ì´ ì¶œë ¥í•˜ëŠ” 오류가 ì—†ë„ë¡ ìˆ˜ì •í•˜ë¼. ë‘ ì–´ì…ˆë¸”ë¦¬ì½”ë“œë¥¼ 비êµí•˜ë¼. ì–´ë–¤ ì°¨ì´ê°€ 있는가? 여러 ìž‘ì€ C í”„ë¡œê·¸ëž¨ì„ (-O2 ê°™ì€) 최ì í™” ì˜µì…˜ì„ ì£¼ê³ ë˜ ì•ˆì£¼ê³ ì»´íŒŒì¼í•´ë³´ë¼. ê²°ê³¼ 어셈블리코드를 ì½ê³ 컴파ì¼ëŸ¬ê°€ 사용한 ê³µí†µëœ ìµœì í™” ê¸°ë²•ì„ ì°¾ì•„ë¼. switch ë¬¸ìž¥ì— ëŒ€í•œ 어셈블리코드를 í•´ì„하ë¼. ì¸ë¼ì¸ asm ë¬¸ìž¥ì´ ìžˆëŠ” 여러 ìž‘ì€ C í”„ë¡œê·¸ëž¨ì„ ì»´íŒŒì¼í•´ë³´ë¼. ì´ëŸ° í”„ë¡œê·¸ëž¨ì˜ ì–´ì…ˆë¸”ë¦¬ì½”ë“œì—는 무슨 ì°¨ì´ê°€ 있는가? ê°ì‹¸ì¸ 함수(nested function)는 다른 함수 ("ê°ì‹¸ëŠ” 함수(enclosing function") 안ì—서 ì •ì˜ë˜ë©°, 다ìŒê³¼ 같다: ê°ì‹¸ì¸ 함수는 ê°ì‹¸ëŠ” í•¨ìˆ˜ì˜ ë³€ìˆ˜ì— ì ‘ê·¼í• ìˆ˜ ìžˆê³ , ê°ì‹¸ì¸ 함수는 ê°ì‹¸ëŠ” í•¨ìˆ˜ì— ì§€ì—ì ì´ë‹¤(local). 즉, ê°ì‹¸ëŠ” 함수가 ê°ì‹¸ì¸ í•¨ìˆ˜ì˜ í¬ì¸í„°ë¥¼ ì œê³µí•˜ì§€ 않는다면 ê°ì‹¸ëŠ” 함수 ë°–ì—서 ê°ì‹¸ì¸ 함수를 í˜¸ì¶œí• ìˆ˜ 없다. ê°ì‹¸ì¸ 함수는 í•¨ìˆ˜ì˜ ë²”ìœ„(visibility)를 ì¡°ì ˆí•˜ê¸°ë•Œë¬¸ì— ìœ ìš©í•˜ê²Œ ì‚¬ìš©í• ìˆ˜ 있다. 아래 ëª©ë¡ 13ì„ ì°¸ê³ í•˜ë¼: #ëª©ë¡ 13 {{{~cpp /* myprint.c */ #include <stdio.h> #include <stdlib.h> int main() { int i; void my_print(int k) { printf("%d\n",k); } scanf("%d",&i); my_print(i); return 0; } }}} ì´ í”„ë¡œê·¸ëž¨ì„ cc -S myprint.c로 컴파ì¼í•˜ê³ 어셈블리코드를 í•´ì„하ë¼. ë˜, ëª…ë ¹ì–´ cc -pedantic myprint.c로 í”„ë¡œê·¸ëž¨ì„ ì»´íŒŒì¼í•´ë³´ë¼. ë¬´ì—‡ì´ ë³´ì´ëŠ”ê°€?