├── assets ├── cpu.gif ├── 2520.png ├── 2522.png ├── 2523.png ├── 2524.png ├── 2525.png ├── 2537.png ├── array.gif ├── model.gif └── emu003.zip ├── incdec.md ├── div.md ├── pointer.md ├── SUMMARY.md ├── gofurther.md ├── introemu8086.md ├── array.md ├── macrofunc.md ├── whyassembly.md ├── mov.md ├── usemov.md ├── compare.md ├── README.md ├── addressing.md ├── howtoemu8086inc.md ├── addsubmul.md ├── stack.md ├── repeat.md ├── logicoperation.md ├── memoryreadwrite.md ├── jump.md ├── variable.md ├── biosinterrupt.md ├── function.md ├── conditionaljump.md ├── number.md ├── 8086arch.md ├── functioncalling.md └── LICENSE /assets/cpu.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurugio/book_assembly_8086_ko/HEAD/assets/cpu.gif -------------------------------------------------------------------------------- /assets/2520.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurugio/book_assembly_8086_ko/HEAD/assets/2520.png -------------------------------------------------------------------------------- /assets/2522.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurugio/book_assembly_8086_ko/HEAD/assets/2522.png -------------------------------------------------------------------------------- /assets/2523.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurugio/book_assembly_8086_ko/HEAD/assets/2523.png -------------------------------------------------------------------------------- /assets/2524.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurugio/book_assembly_8086_ko/HEAD/assets/2524.png -------------------------------------------------------------------------------- /assets/2525.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurugio/book_assembly_8086_ko/HEAD/assets/2525.png -------------------------------------------------------------------------------- /assets/2537.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurugio/book_assembly_8086_ko/HEAD/assets/2537.png -------------------------------------------------------------------------------- /assets/array.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurugio/book_assembly_8086_ko/HEAD/assets/array.gif -------------------------------------------------------------------------------- /assets/model.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurugio/book_assembly_8086_ko/HEAD/assets/model.gif -------------------------------------------------------------------------------- /assets/emu003.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurugio/book_assembly_8086_ko/HEAD/assets/emu003.zip -------------------------------------------------------------------------------- /incdec.md: -------------------------------------------------------------------------------- 1 | #변태 명령어 inc, dec 2 | 3 | inc는 1을 증가하는 명령이고 dec는 1을 감소하는 명령입니다. 이 명령이 왜 필요할까요? 카운트때문입니다. 4 | 5 | C에서 ++, --가 있는데 inc와 dec로 컴파일됩니다. C에서 왜 ++, --라는 연산자를 만들었을까요? 바로 inc, dec를 그대로 사용하기 위해서입니다. ++, --라는 연산자를 당연하게 쓰고 있지만 수학 기호중에 ++,--라는건 없습니다. 즉 뭔가 이유가 있어서 굳이 집어넣은 연산자입니다. inc,dec 어셈블리 명령을 사용하기 위해서 굳이 언어적으로는 말이 안되는 연산자를 만든 것입니다. 6 | 7 | 그만큼 inc,dec는 빠릅니다. 또 1씩 증가/감소하는 연산은 너무나 많이 쓰이지요. 프로세서를 만들 때도 1씩 증감하는 것이 많이 쓰이기때문에 inc,dec라는 명령을 만들었고 C언어를 만들 때도 ++,--를 만든 것입니다. 8 | -------------------------------------------------------------------------------- /div.md: -------------------------------------------------------------------------------- 1 | #분량조절실패로 분리된 나눗셈 2 | 3 | 나눗셈은 곱셈처럼 부호있는 연산이 따로 있고 ax, dx 레지스터가 자동으로 사용된다는 것이 같습니다. 그리고 8비트 16비트 연산을 구별해야한다는 것도 같습니다. 4 | 5 | div는 부호없는 나누기를 합니다. 오퍼랜드가 바이트일 경우 AX값을 나누고 AL에는 결과값이 AH에는 나머지가 저장됩니다. 오퍼랜드가 워드일 경우 DX:AX에 나누기를 실행하고 결과는 AX에 나머지는 DX에 저장됩니다. 6 | 7 | dx:ax = 0:33이고 cx 값이 3일때 div cx를 하면ax=11, dx=0이 되는 식입니다. 8 | 9 | idiv는 부호를 고려한 나누기를 하는 것뿐 다른 것들은 같습니다. 10 | 11 | 나누셈은 별로 자세하게 설명할 필요가 없을것 같습니다. 더 중요한건 div 명령이 아니라 shr, sar 을 쓰는 나눗셈입니다. 이런 shift 처리를 잘 해야 빠르게 곱셈/나눗셈을 하고 비트 연산도 할 수 있는 것입니다. 나눗셈을 주로 쓰는 연산이 좌표를 구할 때나 해시나 큐같이 배열에서 위치 찾을 때, 암호화, 비트 연산 등인데 대부분 일부러 나눗셈 명령을 쓰지 않고 shift 명령을 쓸 수 있도록 2의 배수로 나누기를 합니다. 연산 속도가 훨씬 빠르기 때문입니다. 속도가 깡패지요. 12 | 13 | shift 연산은 다시 자세하게 설명하는게 좋을것 같습니다. 14 | 15 | 이걸로 사칙연산은 끝~ 16 | -------------------------------------------------------------------------------- /pointer.md: -------------------------------------------------------------------------------- 1 | #변수의 주소값 알아내기(포인터) 2 | 3 | LEA라는 새로운 명령어를 말씀드리겠습니다. 특정 변수의 주소를 알아내는 명령입니다. 4 | ``` 5 | ORG 100h 6 | MOV AL, VAR1 ; check value of VAR1 by moving it to AL. 7 | LEA BX, VAR1 ; get address of VAR1 in BX. 8 | MOV BYTE PTR [BX], 44h ; modify the contents of VAR1. 9 | MOV AL, VAR1 ; check value of VAR1 by moving it to AL. 10 | RET 11 | VAR1 DB 22h 12 | END 13 | ``` 14 | var1의 값을 그대로 읽는게 아니라 var1이라는 변수가 위치한 메모리 주소를 알아내는 것입니다. 주소를 저장하는 레지스터(BX, SI, DI, BP)는 메모리 포인터같이 대괄호안에서만 사용될 수 있다는 것을 기억하시기 바랍니다. 15 | 16 | 변수의 주소를 알아내는 것은 중요한 일입니다. 지금은 생각하지 않지만 앞으로 함수를 만들게되면 변수의 주소를 다른 함수로 넘겨서 그 함수에서 변수의 값을 바꾸게 할 것입니다. 변수의 값을 함수에게 전달하면 그 함수는 변수의 값을 바꿀 수가 없습니다. 그 이유는 나중에 함수를 설명할 때 말씀드리겠습니다. mov로 주소 값을 다루는 동작을 설명하면서 lea도 유사한 동작을 하므로 같이 설명했습니다. mov와 lea가 비슷하다고 생각되시면 큰일납니다. 다시한번 예제를 잘 읽어보시고 emu8086으로 실행해보세요. 하나는 변수의 값을 바꾸는 것이고 하나는 변수의 위치를 알아내는 것입니다. -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | * [Introduction](README.md) 4 | * [intro\_emu8086](introemu8086.md) 5 | * [number](number.md) 6 | * [8086\_arch](8086arch.md) 7 | * [mov](mov.md) 8 | * [memory\_read\_write](memoryreadwrite.md) 9 | * [addressing](addressing.md) 10 | * [use\_mov](usemov.md) 11 | * [variable](variable.md) 12 | * [array](array.md) 13 | * [pointer](pointer.md) 14 | * [why\_assembly](whyassembly.md) 15 | * [logic\_operation](logicoperation.md) 16 | * [add\_sub\_mul](addsubmul.md) 17 | * [div](div.md) 18 | * [inc\_dec](incdec.md) 19 | * [jump](jump.md) 20 | * [conditional\_jump](conditionaljump.md) 21 | * [compare](compare.md) 22 | * [repeat](repeat.md) 23 | * [stack](stack.md) 24 | * [function](function.md) 25 | * [function\_calling](functioncalling.md) 26 | * [bios\_interrupt](biosinterrupt.md) 27 | * [macro\_func](macrofunc.md) 28 | * [howto\_emu8086.inc](howtoemu8086inc.md) 29 | * [go\_further](gofurther.md) 30 | 31 | -------------------------------------------------------------------------------- /gofurther.md: -------------------------------------------------------------------------------- 1 | # 어셈블리 공부하는 방법 2 | 3 | 어셈블리를 굳이 한번 공부해보고 싶으시다면 이런 방법으로 해보시기 바랍니다. 요즘 어셈블리 관련해서 어떤 책들이 있는지는 모르겠습니다만 옛날 책들이 많아서 책에 있는 코드가 실행이 안되는 경우가 많을 것입니다. 특히 너무 옛날 책들이 마우스나 키보드, 심지어 플로피 드라이브의 모터 제어같은 내용이 있는데 요즘 Windows7같은 환경에서는 아마도? 실행이 안될 것이므로 쓸데없습니다. 그래서 몇가지 방법들을 추천합니다. 4 | 5 | 1. 리눅스 환경에서 nasm을 가지고 64비트 어셈블리와 시스템콜 호출을 해보세요. gas는 문법이 너무 다르지만 nasm은 emu8086과 문법이 유사해서 편리합니다. 6 | 1. vmplayer나 qemu등의 x86 가상 머신을 가지고 80x86의 부트로더를 만들어보세요. 7 | 1. mmx, sse 같은 멀티미디어 어셈블리를 가지고 이미지 축소나 확대같은 이미지 처리를 만들어보세요. 압축이나 암호화 알고리즘에도 많이 쓰이는 방법입니다. 8 | 1. 운영체제 만들기 책들이 몇개 있으니 한번 도전해보세요. 9 | 1. 솔직히 여기까지 읽어보셨다면 이제 어떤 어셈블리 코드라도 대강 이해할 수 있으실 겁니다. 여기서 접으시고 나중에 실무에 필요할 때 펜티엄 메뉴얼같은거 보시면서 어떤 명령어인지 참고하시면 어떤 코드든 이해하실 수 있습니다. 포기하세요. 편해요 ;-) 10 | 11 | 굳이 더 계속 하고 싶으신 분들을 위한 참고 자료: 12 | 13 | http://www.drpaulcarter.com/pcasm/ : NASM 어셈블러를 이용해서 부트로더 만드는 문서인데 설명이 쉽고 자세하고 따봉입니다. 14 | 15 | http://programminggroundup.blogspot.kr/ : programming ground up이라는 문서인데 예전에 번역해놓은 것도 있습니다. 리눅스 환경에서 시스템 프로그램하기 입니다. 16 | 17 | Greate Code 책: 말 그대로 Greate 합니다. 프로그래머들은 꼭 봐야합니다. 18 | 19 | http://gurugio.kldp.net 20 | -------------------------------------------------------------------------------- /introemu8086.md: -------------------------------------------------------------------------------- 1 | 태초의 언어를 배우기에는 지금 우리가 가진 컴퓨터가 너무 복잡합니다. 2 | 태초의 언어를 배우기에 좋은게 태초의 컴퓨터가 아닐까요? 3 | 그래서 태초의 컴퓨터에 들어가던 8086 프로세서를 사용하겠습니다. 4 | 8086 프로세서는 용산에있는 컴퓨터 부품 가게들 (비디오카드나 메모리같은 부품이 아니라 칩을 말합니다)에 가면 5 | 몇천원에 살 수 있습니다. 그런데 8086 프로세서만 있다고해서 뭘 할 수 있는게 아니지요. 6 | 8086에 프로그램을 전달하고 또 프로그램의 실행 결과를 보려면 입출력 장치가 필요합니다. 7 | 8086에 키보드나 모니터를 달아야겠지요. 8 | 그런 수고를 덜어서 우리 컴퓨터에서 8086을 실험하게 해주는게 emu8086이라는 프로그램입니다. 9 | 10 | 11 | 사실 제가 학부때는 8086 프로세서와 SRAM 등의 칩들을 직접 사다가 전선을 납땜하고 숫자 키패드와 텍스트 LCD 를 연결해서 컴퓨터를 직접 만드는 수업이 있었습니다. 지금도 있는지는 모르겠네요. 그렇게 8086 컴퓨터를 만들면 컴퓨터의 역사에서 볼 수 있는 최초의 계산기나 애플1과 동일한 물건이 됩니다. 12 | 우리는 emu8086으로 아주 간단하게 태초의 컴퓨터를 체험해볼 수 있는 것이지요. 13 | 14 | 가장 먼저 emu8086을 다운받아서 압축을 풀겠습니다. 예전에는 개발한 회사 홈페이지가 있었는데 지금은 회사가 문을 닫았나봅니다. 개발자가 직접 배포하고 있네요. 15 | 16 | (다운로드 링크가 닫혀서 제 홈페이지에 파일을 올렸습니다) 17 | 18 | http://gurugio.kldp.net/wiki/wiki.php/asm_basic?action=download&value=emu003.zip 19 | 20 | 21 | 압축을 풀면 emu003디렉토리에 emu8086.exe라는 파일이 있습니다. 실행하면 작은 텍스트 창이 나타납니다. file이라는 메뉴가 있는데 메뉴중에 emulate가 있습니다. 눌러보면 다음과 같이 뭔가 실행된듯한 하지만 이해할 수 없는 화면이 나타납니다. 이제 8086을 가지고 놀 준비가 되었습니다. 22 | 23 | 24 | ![](/assets/2520.png) 25 | 26 | 27 | 제 홈페이지에 예전에 썼던 글입니다. 참고하세요. http://gurugio.kldp.net/wiki/wiki.php/emu8086_basic 28 | 29 | -------------------------------------------------------------------------------- /array.md: -------------------------------------------------------------------------------- 1 | #배열 2 | 3 | 배열은 연속된 변수들로 볼 수 있습니다. 연속되었다는 말은 메모리상에서의 위치가 연속적으로 자리를 잡고 있다는 뜻입니다. 1000h 번지에 위치는 8비트 변수가 있고 1001h에도 있고 1002h에도 있다면 이 3개의 변수를 합쳐서 하나의 배열로 만드는 것이지요. 배열이란 변수들 여러개를 붙여놓은 것입니다. 4 | 예를 들어 문자열은 바이트 배열입니다. 각각의 바이트는 출력하려는 문자의 ASCII 코드값을 나타냅니다. 1바이트 문자라는 변수들을 여러개 붙여놓은 것이지요. 5 | 6 | 여기 배열을 선언하는 예를 보겠습니다. 7 | 8 | a DB 48h, 65h, 6Ch, 6Ch, 6Fh, 00h 9 | b DB 'Hello', 0 10 | 11 | b 는 a배열과 정확히 같습니다. 컴파일러는 따옴표안에 있는 문자열을 바이트의 집합으로 변경합니다. 아래 차트는 이 배열이 선언되었을 때 메모리를 보여줍니다. 문자열의 마지막은 문자가 아니라 0이라는 것에 주의하세요. 0은 문자열이 끝났다는 것을 말해줍니다. 0이 없다면 문자열을 읽다가 문자열이 언제 끝나는지 알수가 없으니까요. 12 | 13 | ![](/assets/array.gif) 14 | 15 | 배열의 원소는 []를 사용하여 접근할 수 있습니다. 예를 들면: 16 | 17 | ``` 18 | MOV AL, a[3] 19 | ``` 20 | 21 | 이렇게 실행하면 al에는 6ch 값이 저장됩니다. 22 | 23 | 또는 메모리 인덱스 레지스터(BX, SI, DI, BP)를 사용할 수도 있습니다. 24 | ``` 25 | MOV SI, 3 26 | MOV AL, a[SI] 27 | ``` 28 | 큰 배열을 선언할 때, DUP 연산자를 사용할 수 있습니다. DUP의 문법은 다음과 같습니다. 29 | ``` 30 | 숫자 DUP ( 값(s) ) 31 | 숫자 - 복사할 횟수 (상수값이어야 함). 32 | 값 - 복사할 데이터 33 | ``` 34 | 예를 들어 35 | ``` 36 | c DB 5 DUP(9) 37 | ``` 38 | 는 다음과 같습니다. 39 | ``` 40 | c DB 9, 9, 9, 9, 9 41 | ``` 42 | 43 | 예를 하나 더 들면 44 | ``` 45 | d DB 5 DUP(1, 2) 46 | ``` 47 | 는 다음과 같습니다. 48 | ``` 49 | d DB 1, 2, 1, 2, 1, 2, 1, 2, 1, 2 50 | ``` 51 | 52 | 물론, 255보다 크거나 -128보다 작은 값이 필요한 경우, DB 대신 DW를 사용할 수 있습니다. DW는 문자열을 선언할 때는 사용할 수 없습니다. 53 | 문자열과 메모리 접근을 배웠으니 스크린에 hello, world를 출력하는 예제를 한번 만들어보세요. -------------------------------------------------------------------------------- /macrofunc.md: -------------------------------------------------------------------------------- 1 | #매크로 함수 만들기 2 | 이전에 설명했듯이 매크로는 함수처럼 코드가 호출되는게 아니라 코드가 복사되서 들어갑니다. C의 preprocessor나 인라인 함수와 같습니다. 3 | 4 | 매크로는 이렇게 만듭니다. 5 | ``` 6 | name MACRO [parameters,...] 7 | 8 | 9 | 10 | ENDM 11 | ``` 12 | 13 | MACRO라는 키워드로 매크로를 선언하고 인자들도 지정할 수 있습니다. 14 | 15 | 예를 보면 16 | 17 | ``` 18 | MyMacro MACRO p1, p2, p3 19 | 20 | MOV AX, p1 21 | MOV BX, p2 22 | MOV CX, p3 23 | 24 | ENDM 25 | 26 | ORG 100h 27 | 28 | MyMacro 1, 2, 3 29 | 30 | MyMacro 4, 5, DX 31 | 32 | RET 33 | ``` 34 | 35 | 이렇게 매크로를 만들어서 쓸 수 있습니다. 함수처럼 call을 쓸 필요없이 매크로 이름만 쓰면 됩니다. 매크로는 어셈블러의 키워드이지 프로세서의 명령어가 아니니까요. 36 | 37 | 매크로가 어셈블되면 이렇게 됩니다. 38 | ``` 39 | MOV AX, 00001h 40 | MOV BX, 00002h 41 | MOV CX, 00003h 42 | MOV AX, 00004h 43 | MOV BX, 00005h 44 | MOV CX, DX 45 | ``` 46 | 대강 어떻게 쓰는지는 간단합니다. 그런데 왠지 함수를 만드는 것보다 더 고급스럽고 더 함수같아 보이는건 기분탓입니다 ;-) emu8086말고 최신 어셈블러를 쓰면 함수를 만들 때도 함수 인자를 지정하거나하는 기능을 제공합니다. 그래서 거의 C처럼 어셈블리 프로그래밍을 할 수 있습니다. 47 | 48 | 만약에 매크로 내부에서 점프를 해야될 일이 있다면 label을 어떻게 만들어야할까요? 매크로는 코드의 반복이므로 같은 이름의 label도 반복적으로 나타나게되서 어셈블러에서 에러가 납니다. 그럴때는 다음과 같이 local이라는 키워드를 사용하면 됩니다. 49 | ``` 50 | MyMacro2 MACRO 51 | LOCAL label1, label2 52 | 53 | CMP AX, 2 54 | JE label1 55 | CMP AX, 3 56 | JE label2 57 | label1: 58 | INC AX 59 | label2: 60 | ADD AX, 2 61 | ENDM 62 | 63 | 64 | ORG 100h 65 | 66 | MyMacro2 67 | 68 | MyMacro2 69 | 70 | RET 71 | 72 | END 73 | ``` -------------------------------------------------------------------------------- /whyassembly.md: -------------------------------------------------------------------------------- 1 | #어셈블리를 하는 이유 2 | 3 | 누가 저한테 와서 "야 요즘 총들은 다 칼빈의 후손일 뿐이야. 칼빈을 분해 조립해서 수리할 수 있으면 무슨 총이든 다 고칠 수 있어"라고 말하면 4 | 5 | 저는 "칼빈에 대한 의리?"라고 할겁니다. 6 | 7 | 의리말고는 이 시대에 칼빈 총을 어렵게 구해서 분해해보고 닦고 조이고 기름칠 이유가 없는 것이지요. 8 | 9 | 이 시대에 어셈블리 언어를 공부하는 것도 마찬가지 아닌가 싶습니다. 10 | 11 | 더욱이 이제는 NASA에서도 안쓰는 8086용 어셈블리 언어는 아무리 의리가 좋다고해도 좀 그만 했으면 할 수도 있지요. 12 | 13 | 제가 처음 어셈블리를 배운 이유는 대학 입학 후 프로그래밍과 컴퓨터 구조 등을 처음 배우다보니 설명을 안하고 그냥 그렇다라는 식으로 넘어가는게 너무 많아서 였습니다. 사실 컴퓨터라는 것은 실리콘 물질의 전기적인 특성부터 시작해서 전자적인 신호, 논리적인 회로, 디지털 회로로 이루어져있고, 그럼 컴퓨터에 프로그래밍하는 것은 어셈블리 언어부터 그래픽, 수치 연산같은 순수 학문까지 범위가 넓습니다. 이런걸 학부생이 알아간다는건 무리가 있고 선을 긋고 한 분야씩 배워야 하는 것이지요. 어쨌든 저는 그때에는 그런걸 모르고 그냥 막연히 어셈블리 언어를 배우면 컴퓨터의 동작 원리도 배우고 프로그래밍의 원리도 배울 수 있을거라고 생각했습니다. 그리고 그당시 제가 원했던 것 이상으로 배울 수 있었지요. 14 | 15 | 배운게 많은 뿐 아니라 즐거운 일이었습니다. 좋은 분들께 많은 것을 쉽게 배울 수 있었고 많은 좋은 추억들도 생겼구요. 공부한 것보다는 사실 좋은 사람들과 즐겁게 뭔가를 했다는게 더 기억이 남습니다. 16 | 17 | 비슷한 시기에 리눅스 커널이나 자바같이 요즘 돈되는 분야에 투자하는 분들 중에는 지금 저는 오를 수 없는 경지에 오르시거나 제가 만질 수 없는 돈을 만지신 분들이 계십니다. 물론 제가 자바를 했다고해서 그분들처럼 많은 몸값을 받았으리라는 보장은 없지요. 하지만 그럴 기회가 없긴 했습니다. 취업도 잘 안되서 도피성? 대학원 진학도하고 평범한 개발자로 살아가고 있습니다. 18 | 19 | 세상에 대한 의미는 자기가 부여하는 만큼이라고 누가 말한게 기억나는데요. 어셈블리도 제가 나쁜 의미를 부여하면 다른 좋은 기회에 대한 비용으로 의미가 남을 것이고, 좋은 의미를 부여한다면 많은 것을 배우게해준 기회로 의미가 남을 것입니다. 20 | 21 | 어셈블리 언어를 공부하시는건 자기가 부여한 만큼의 의미가 남습니다. C언어의 배경 지식으로 의미를 부여하시면 그만큼이 되는 것이고, 재미로 알아보는 컴퓨터의 역사로 의미를 부여하시면 그만큼의 되는 것이지요. 자신만의 의미를 부여해야할 만큼 세상이 알아주지 않는 주제이니까요 한번쯤은 왜 어셈블리 언어를 위해 다른 소중한 것들을 할 수 있는 시간을 투자해야할까를 생각해보는 것도 좋을것 같습니다. 22 | 23 | 어찌보면 회사 일로 강제로 해야되는게 아닌 이상 모든 취미가 다 그렇네요. 내가 어떤 의미를 부여하냐에따라 남들은 무의미하게 생각하는 작은 것들이 내게는 인생의 낙이 되니까요. -------------------------------------------------------------------------------- /mov.md: -------------------------------------------------------------------------------- 1 | # mov 명령어 2 | 3 | 이제서야 실습을 하게 되었습니다. 16진수, 2진수와 8086 구조에 대한 글을 보면서 하나도 모르겠다고 생각하셔도 전혀 상관없습니다. 원래 처음 뭔가를 배운다는게 내가 뭘 모르는지 모르는 상태에서 뭘 모르는지 아는 상태로 옮겨가는 거라고 합니다. 이제 "16진수, 2진수, 8086의 구조에 대해서 모르겠다"라는 것을 아신 것입니다. 그러니 진일보한 것이지요. 4 | 5 | 실습을 하면 16진수,2진수,레지스터들에 대해서 좀더 이해해보겠습니다. 6 | 7 | 일단 emu8086의 압축을 풀었던 디렉토리를 열어봅니다. emu8086.exe 파일이 보이지만 지금은 emu8086.exe가 아니라 cmdhere.cmd 파일을 실행합니다. 그러면 터미널 창이 나타납니다. 아마 터미널을 처음 보신 분들도 계실것 같습니다. 당황하지않고 notepad라고 키보드로 칩니다. 그리고 엔터를 치면 메모장이 실행될 것입니다. 메모장에 실습할 어셈블리 명령어들을 입력합니다. 8 | 9 | ``` 10 | mov ax, 1000 11 | mov bx, 1000h 12 | mov cx, 0ffffh 13 | ``` 14 | 15 | 16 | 그리고 파일을 저장합니다. 이름은 상관없지만 키보드로 입력하기 편하게 mov.txt로 하겠습니다. 그리고 다시 터미널에 들어가서 emu8086.exe mov.txt 라고 입력합니다. 그럼 emu8086이 실행되면서 텍스트 편집기에 우리의 예제 소스가 나타납니다. 바로 emulate 메뉴를 실행합니다. 17 | 18 | 일부러 화면을 캡처하지 않겠습니다. 한번 해보세요. 에물레이터 화면을 보면 왼쪽에 레지스터 값이 나타나고 오른쪽에는 메모리 값과 메모리에 있는 어셈블리 명령어가 나타납니다. 우리가 입력한 mov ax, 1000이 있나요? 아마 없을 겁니다. mov bx, 1000h는 있나요? 있습니다. 무슨 차이일까요? 19 | 20 | 이전 글에서 프로세서는 2진수만 알 수 있고 어셈블리 명령에서는 2진수를 보기쉽게 만드는 16진수만을 쓴다고 말씀드렸습니다. 바로 지금 우리가 입력한 10진수가 16진수로 어셈블링되서 프로세서에 저장된 것을 볼 수 있습니다. 10진수 1000이 16진수로 3e8h입니다. 그래서 명령어 화면에 mov ax, 3e8h로 나타난 것입니다. 1000h는 16진수이므로 명령어 화면에 그대로 mov bx, 1000h로 나타납니다. 0ffffh도 마찬가지로 그대로 나타나구요. 21 | 22 | 그리고 single step 버튼을 눌러보세요. 레지스터 값이 바뀌나요? step back 버튼도 눌러보세요. 그리고 다른 버튼들도 눌러서 어떻게 실행되나 해보세요. 23 | 24 | 어셈블리 명령이 뭔지 레지스터에 어떻게 값을 쓰는지 알것 같으신가요? 이렇게 레지스터에 직접적으로 값을 쓰는 명령어가 mov입니다. 25 | 26 | 레지스터에 데이터를 써놨는데요 이번에는 다른걸 해보시기 바랍니다. 레지스터의 값을 다른 레지스터로 옮기는건 어떻게 하면 될까요? 27 | 28 | 그리고 CS, IP, SS, SP 등등 범용 레지스터가 아닌 특수한 레지스터들에 값을 한번 써보세요. 특수한 레지스터와 범용 레지스터간에 값을 옮겨보세요. 29 | 30 | -------------------------------------------------------------------------------- /usemov.md: -------------------------------------------------------------------------------- 1 | #mov의 사용법 정리 2 | 3 | mov 명령의 사용법을 총정리해보겠습니다. 4 | 5 | 일반적으로 메모리나 레지스터에 값을 읽고 쓰는 경우는 이렇습니다. 6 | 7 | ``` 8 | MOV REG, 메모리주소 9 | MOV 메모리주소, REG 10 | MOV REG, REG 11 | MOV 메모리주소, 상수 12 | MOV REG, 상수 13 | ``` 14 | 15 | 16 | reg는 레지스터를 말하는 것이고 상수는 0b800h같이 직접 숫자를 쓴 것을 말합니다. 17 | 18 | mov dx, [bx]를 하면 메모리에서 16비트 값을 읽어서 dx 레지스터에 저장하는 것입니다. 19 | 20 | 그런데 한가지 주의할 것이 있습니다. mov [bx], 0ffh 라고 쓰면 어떻게 될까요? 21 | 22 | [bx]의 메모리에 00ffh의 16비트를 쓰려는 것일까요? 아니면 0ffh의 8비트 값을 쓰려는 것일까요? 알아보려면 역시 실험을 해봐야겠지요. 23 | 24 | ``` 25 | org 100h 26 | mov ax, 0b800h 27 | mov ds, ax 28 | mov bx, 0 29 | mov cl, 'A' 30 | mov ch, 11011111b 31 | mov ds:[bx], cx 32 | mov [bx+2], cx 33 | mov si, 4 34 | mov [bx+si], cl 35 | mov [bx+si+2], cx 36 | mov [bx], 0ffh 37 | ret 38 | ``` 39 | 40 | 41 | 이전 글에서 실험한 소스에 마지막으로 mov [bx], 0ffh를 추가한 것입니다. 0ffh를 쓰려는 주소는 0b8000h 이겠지요? 이 소스를 실험하기 위해서 에물레이터를 실행한 후에 에물레이터 화면 아래쪽에 있는 aux 버튼을 눌러봅니다. 또다른 메뉴가 나올겁니다. 메뉴에서 memory를 선택합니다. 그럼 아래 그림처럼 Random Access Memory라는 창이 나타납니다. 42 | 43 | ![](/assets/2522.png) 44 | 45 | update 버튼 왼쪽에 주소 값이 보이지요? 0700:0100으로 표시되어있을 것입니다. 여기를 클릭해서 주소 값을 지우고 b800:0000을 입력하고 update 버튼을 누릅니다. 어떤가요? 우리가 데이터를 쓰려는 메모리 위치에 어떤 값들이 저장되어있는지 나타납니다. 이제 single step으로 명령 하나씩 실행해보세요. 메모리에 값들이 바뀌는걸 확인할 수 있습니다. 그리고 mov [bx], 0ffh 명령을 실행하면 16비트 값이 바뀌나요? 아니면 8비트 값이 바뀌나요? 46 | 47 | ![](/assets/2523.png) 48 | 49 | 원래 써있던 41이라는 값이 FF로 변했습니다. 한바이트의 값만 변했습니다. 50 | 51 | 에물레이터 창에서 어셈블리 명령들이 출력된 칸을 보면 mov b.[bx], 0ffh라고 표시되는 것을 보실수 있습니다. 이 말은 mov byte ptr [bx], 0ffh를 줄여서 표시한 것입니다. byte ptr 이라는 말은 이 주소에 한 바이트만을 쓰겠다라는 표시입니다. 16비트를 쓰고 싶다면 word ptr [bx] 라고 쓰면 되지요. 52 | 53 | 그럼 mov byte ptr [bx], 0ffh 로 바꿔서도 해보고, mov word ptr [bx], 0ffh로 바꿔서도 실험해보세요. 한바이트 값이 바뀌기도 하고 두바이트 값이 바뀌기도 할 것입니다. 54 | 55 | -------------------------------------------------------------------------------- /compare.md: -------------------------------------------------------------------------------- 1 | #같은지 다른지 비교하기 2 | 3 | 수를 비교하는 cmp 명령을 설명하겠습니다. 너무너무너무 많이 사용되는 명령이라 아무리 강조해도 지나치지 않은 명령입니다. 그리고 다시한번 말씀드리지만 동작하는 방식은 빼기입니다. 단지 뺄셈을 해서 결과를 저장하는게 아니라 플래그 비트만 바꾼다는게 차이입니다. 4 | 5 | 5-2 연산을 하면 zf가 0이 되겠지요. 따라서 cmp 5, 2를 하면 zf가 0이 됩니다. cmp 7, 7을 하면 zf가 1이 되는 것입니다. 6 | 7 | 좀 복잡한 예제를 실행해보겠씁니다. emu8086.inc라는 파일을 include한다는게 뭔가 C 코드같은 느낌이 납니다. 별거는 아니고 putc라는 매크로 함수를 미리 코딩해놓은 파일입니다. putc외에도 다양한 매크로 함수들이 있습니다만 지금은 볼 필요가 없습니다. 지금은 putc가 문자를 화면에 출력하는 일을 한다는 것만 알면 됩니다. 에물레이터를 실행한 다음 screen 버튼을 눌러서 어떤 문자가 출력되는지 확인하시기 바랍니다. 8 | ``` 9 | include "emu8086.inc" 10 | 11 | org 100h 12 | 13 | mov al, 25 ; set al to 25. 14 | mov bl, 10 ; set bl to 10. 15 | 16 | cmp al, bl ; compare al - bl. 17 | 18 | je equal ; jump if al = bl (zf = 1). 19 | 20 | putc 'n' ; if it gets here, then al <> bl, 21 | jmp stop ; so print 'n', and jump to stop. 22 | 23 | equal: ; if gets here, 24 | putc 'y' ; then al = bl, so print 'y'. 25 | 26 | stop: 27 | 28 | ret ; gets here no matter what. 29 | ``` 30 | 31 | 첫 cmp 명령은 25와 10을 비교합니다. 당연히 zf는 0이고 je 명령을 만나도 점프를 하지 않겠지요. 그리고 putc 'n' 매크로가 실행되서 화면에 n이 출력됩니다. 에물레이터에서 single step으로 실행하면서 어셈블된 명령어들을 보면 push, pop, int 같은 아직 모르는 명령어들이 나오는데 그냥 넘어갑시다. putc 매크로 함수가 어셈블된 것입니다. 32 | 화면에 n이 출력된 후 stop으로 점프합니다. 그리고 프로그램이 끝납니다. 만약 25를 10으로 바꾸면 y가 출력되겠지요. 33 | 참고로 매크로 함수와 그냥 함수의 차이를 말씀드리면 매크로라는 것은 실행 코드가 호출된 위치에 들어간다는 것입니다. putc를 호출한 곳에 putc의 코드가 들어갑니다. putc를 호출하는게 아닙니다. 변수나 label처럼 이름을 만나면 해당되는 코드로 바꿔치기를 하는 것입니다. 일반 함수는 함수를 호출하는 코드가 들어갑니다. 그리고 다시 호출 명령으로 복귀합니다. 매크로가 많아지면 코드가 길어지겠지요. 함수를 아무리 많이 호출해도 코드가 길어지지 않습니다. 왔다갔다만 열심히 하니까요. 매크로는 왔다갔다를 하지 않습니다. 왔다갔다할 시간을 아낄 수 있지요. 그래서 좀더 빠릅니다. 코드의 길이와 실행 속도의 trade off가 있습니다. 보통 짧은 코드는 매크로로 만들고 긴 코드는 함수로 만듭니다. 어느 언어나 마찬가지입니다. 34 | 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 태초의 프로그래밍 언어 어셈블리 2 | 3 | (This is Korean language version) 4 | 5 | 컴퓨터 세계의 태초에 말씀이 있었으니 어셈블리 언어입니다. 6 | 프로그래밍 언어라기에는 너무 컴퓨터에 가까워서 언어라고 하기 어렵고 7 | 그렇다고 0과 1로 이루어진 컴퓨터 신호도 아닌 이상한 언어입니다. 8 | 태초의 언어이므로 컴퓨터의 세부적인 동작 하나하나를 직접 지시할 수 있습니다. 9 | 그렇기 어셈블리 언어를 배운다는 것은 프로그래밍을 배운다기보다는 10 | 컴퓨터 그 자체를 배우는 것에 가깝습니다. 11 | 그런데 그렇게 컴퓨터 그 자체를 배우다보면 세상 모든 프로그래밍 언어가 12 | 어떤 원리로 돌아가는지 알게됩니다. 13 | 마치 매트릭스 세계에서 네오가 세상을 코드로 보는 것처럼 14 | 컴퓨터 세상의 모든게 어셈블리 기호들로 보이기 시작합니다. 15 | 그래서 너무나 재밌습니다. 16 | 복잡한 컴퓨터 세상을 이루는 원리들이 눈에 보인다는 것은 새로운 세상이 열리는것 같지요. 17 | 18 | 19 | 어셈블리 언어를 배우면 프로그래밍을 더 잘하거나 실력이 좋아진다는 말은 못합니다. 20 | 저부터가 프로그래밍을 잘하거나 뛰어난 개발자가 아니기 때문입니다. 21 | 그래도 처음 어셈블리 언어를 배우고 컴퓨터 세상에 대해 새롭게 눈뜬 순간의 흥분은 22 | 아직도 저를 이끌어서 리눅스 커널과 컴퓨터 구조를 공부하게 만들고 있습니다. 23 | 많은 분들이 세상을 다르게 볼 수 있는 기회를 가지셨으면 합니다. 24 | 25 | 참고로 나중에 기회가 되면 어셈블리와는 완전히 반대로 완전히 추상적인 언어인 LISP에 대해서도 26 | 글을 쓰게 되길 바랍니다. 어셈블리로 컴퓨터 세상에 대한 눈을 떴다면 27 | LISP을 공부한 후로는 컴퓨터 세상과 실제 우리가 사는 세상을 하나로 볼 수 있게 되었습니다. 28 | 29 | PS: 처음에는 오픈튜토리얼사이트에 올렸던 강좌인데 제가 해외에있다보니 접속이 잘 안되서 gitbook 사이트로 옮긴 글들입니다. 강좌 원본은 여기 있습니다. 30 | https://opentutorials.org/module/1087 31 | 32 | PS: 리눅스 환경에서 32비트 어셈블리로 시스템 프로그래밍을 하는 문서입니다. http://www.joinc.co.kr/modules/moniwiki/wiki.php/Site/Assembly/Documents/ProgrammingGroundUp/index.html 33 | 34 | PS: 진지하게 어셈블리를 공부하고싶으시면 Art of Assembly Programming을 추천합니다. 1년 정도는 재미있을 수 있습니다. 35 | 36 | PS: 전설적인 The Art Of Computer Programming도 어셈블리 유사 언어로 써진 책입니다. 죽기전에 한번쯤은 도전해보자구요 ;-) 37 | 38 | # 목차 39 | * [Introduction](README.md) 40 | * [intro\_emu8086](introemu8086.md) 41 | * [number](number.md) 42 | * [8086\_arch](8086arch.md) 43 | * [mov](mov.md) 44 | * [memory\_read\_write](memoryreadwrite.md) 45 | * [addressing](addressing.md) 46 | * [use\_mov](usemov.md) 47 | * [variable](variable.md) 48 | * [array](array.md) 49 | * [pointer](pointer.md) 50 | * [why\_assembly](whyassembly.md) 51 | * [logic\_operation](logicoperation.md) 52 | * [add\_sub\_mul](addsubmul.md) 53 | * [div](div.md) 54 | * [inc\_dec](incdec.md) 55 | * [jump](jump.md) 56 | * [conditional\_jump](conditionaljump.md) 57 | * [compare](compare.md) 58 | * [repeat](repeat.md) 59 | * [stack](stack.md) 60 | * [function](function.md) 61 | * [function\_calling](functioncalling.md) 62 | * [bios\_interrupt](biosinterrupt.md) 63 | * [macro\_func](macrofunc.md) 64 | * [howto\_emu8086.inc](howtoemu8086inc.md) 65 | * [go\_further](gofurther.md) 66 | 67 | -------------------------------------------------------------------------------- /addressing.md: -------------------------------------------------------------------------------- 1 | #메모리 주소 표현 방법들 2 | 3 | 이전 글에서 ds:[bx]라는 가장 간단한 메모리 주소 표현 방법을 보았습니다. 이번에는 그 외에 어떤 방법들이 있는지를 보겠습니다. 4 | 5 | 다음 테이블이 바로 메모리 주소의 모든 표현 방식을 모아놓은 것입니다. 6 | 7 | ``` 8 | [BX + SI] 9 | [BX + DI] 10 | [BP + SI] 11 | [BP + DI] 12 | [SI] 13 | [DI] 14 | d16 (변수의 오프셋) 15 | [BX] 16 | [BX + SI + d8] 17 | [BX + DI + d8] 18 | [BP + SI + d8] 19 | [BP + DI + d8] 20 | [SI + d8] 21 | [DI + d8] 22 | [BP + d8] 23 | [BX + d8] 24 | [BX + SI + d16] 25 | [BX + DI + d16] 26 | [BP + SI + d16] 27 | [BP + DI + d16] 28 | [SI + d16] 29 | [DI + d16] 30 | [BP + d16] 31 | [BX + d16] 32 | ``` 33 | 34 | d8은 8비트의 숫자를 말하고 d16은 16비트의 숫자를 말합니다. 35 | 36 | 가만히 보면 첫번째에 나오는 레지스터가 bx, bp이고 그 다음이 si,di 이고 그 다음이 숫자라는 규칙이 있는걸 알 수 있습니다. bx, bp의 이름이 베이스 레지스터, 베이스 포인터 레지스터라는 것을 보면 bx, bp는 항상 기본 주소를 나타낸다는 것을 알 수 있습니다. 또 si, di의 이름이 소스 인덱스 레지스터, 목적destination 인덱스 레지스터라는 것을 보면 베이스 주소에 더하는 인덱스라는 것이 생각납니다. C언어 하시는 분들은 뭔가 생각나는거 없으세요? 힌트는 배열입니다. (주1) 37 | 38 | 39 | 40 | 실습 코드를 보겠습니다. 41 | ``` 42 | org 100h 43 | mov ax, 0b800h 44 | mov ds, ax 45 | mov bx, 0 46 | mov cl, 'A' 47 | mov ch, 11011111b 48 | mov ds:[bx], cx 49 | mov [bx+2], cx 50 | mov si, 4 51 | mov [bx+si], cx 52 | mov [bx+si+2], cx 53 | ret 54 | ``` 55 | 56 | 일단 실행하기 전에 코드를 보면서 A라는 글자가 화면에 몇번 나올지 생각해보세요. 메모리에 데이터를 쓰는 명령이 몇개 있나요? 4개입니다. [] 표시가 있는 명령어만 보면 되니까요. 57 | 58 | 그럼 메모리 주소가 각각 몇번지가 될까요? 59 | 60 | 첫번째로 메모리에 쓰는 명령어는 mov ds:[bx], cx입니다. 이때 ds값은 0b800h이고 bx 값은 0입니다. 세그먼트 주소 * 10h + 레지스터 값이 주소값이므로 0b8000h + 0h = 0b8000h 값이 주소 값입니다. 0b8000h 위치의 메모리에 'A'라는 값을 쓴 것입니다. 61 | 62 | 그 다음 명령어는 [bx+2]입니다. 세그먼트 ds 값은 0b800h이고 레지스터 값 bx에 2가 더해졌으니 0b8000h + 0 + 2 = 0b8002h 가 주소 값이 됩니다. 앞에서 쓴 메모리 주소보다 2가 더해졌지요? 63 | 64 | 그 다음은 bx+si인데 si에는 4라는 값이 있습니다. 그래서 0b8000h + 0 + 4 = 0b8004h이 메모리 주소가 됩니다. 그 다음은 bx+si+2 이므로 계산할 필요도 없이 0b8006h가 되겠지요. 65 | 66 | 한번 실행해보세요. 핑크색 A가 몇개 나왔나요? 67 | 68 | 핑크색 A들이 서로 붙어있나요 아니면 떨어져있나요? 69 | 70 | 예제에 없는 다른 형태들을 사용해서 한줄을 A로 채워보세요. 그리고 그 다음줄로 넘어가 보세요. 메모리 주소에 계속 2를 더하면 한줄 한줄 채울 수 있습니다. 색깔도 바꿔보고 글자도 다른 글자로 바꿔보세요. 색깔을 지정하는 11011111b 값을 아무 값으로나 바꿔서 해보세요. 71 | 72 | 잘된다면 이번엔 안되는걸 해보세요. d8 위치에 16비트 값을 넣어보세요. 레지스터 2개를 이용하는 [bx + si] 형태말고 [ax + bx + cx] 같이 3개의 레지스터를 써보세요. 테이블을 보면 항상 첫번째 레지스터가 bx, bp, si, di 레지스터인데 ax나 cx같이 다른 레지스터를 써보세요. 어떻게 되나요? 73 | 74 | 75 | --- 76 | 주1: 베이스 주소 + 인덱스 주소라는걸 보면 포인터 연산이 생각나야 합니다. 안그러면 C언어 개발자라고 하기에 부끄러운 겁니다. 진짜에요. 저도 포인터 연산을 생각못해서 절 가르쳐준 선배님에게 혼난 기억이 있거든요. *(p + 1), *(p + 3) 이런걸 생각해보세요. C언어가 왜 어셈블리 언어를 계승하는 언어인지 아시겠지요? 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /howtoemu8086inc.md: -------------------------------------------------------------------------------- 1 | #emu8086.inc 사용법 2 | 3 | emu8086 프로그램이 자체적으로 제공하는 라이브러리가 있습니다. emu8086.inc 입니다. include "emu8086.inc"라는 키워드만 사용하면 라이브러리를 사용할 수 있습니다. 4 | 5 | ##매크로 6 | 7 | 제공되는 매크로 몇가지를 소개합니다. 8 | 9 | * PUTC char - 한개의 파라미터를 가진 매크로, 현재위치의 아스키 문자를 출력함 10 | * GOTOXY col, row - 두개의 파라미터를 가진 매크로, 커서위치를 정함 11 | * PRINT string - 한개의 파라미터를 가진 매크로, 문자열을 출력함 12 | * PRINT string - 한개의 파라미터를 가진 매크로, 문자열을 출력함, PRINT와 같지만 문자열 끝에 '캐리지 리턴'을 자동으로 추가해줌 13 | * CURSOROFF - 텍스트 커서 해제 14 | * CURSORON - 텍스트 커서 시작 15 | 16 | 이렇게 사용하면 됩니다. 17 | ``` 18 | include "emu8086.inc" 19 | 20 | ORG 100h 21 | 22 | PRINT 'Hello World!' 23 | 24 | GOTOXY 10, 5 25 | 26 | PUTC 65 ; 65 - is an ASCII code for 'A' 27 | PUTC 'B' 28 | 29 | RET ; return to operating system. 30 | END ; directive to stop the compiler. 31 | ``` 32 | 33 | 34 | ##함수 35 | 36 | 제공되는 함수들도 있는데 몇가지를 소개합니다. 37 | 38 | 39 | * PRINT_STRING - 현재 커서부터 NULL까지 출력하고 DS:SI레지스터에서 문자열의 주소를 받는 프로시져. 40 | * 사용법 : END 지시어전에 DEFINE_PRINT_STRING 선언 41 | * PTHIS - 현재 커서부터 NULL까지 출력하지만(PRINT_STRING처럼) 스택에서 문자열의 주소를 받는 프로시져. CALL 명령어 바로다음에 ZERO TERMINATED를 정의해야한다. 42 | * ex) CALL PTHIS db 'Hello World', 0 43 | * 사용법 : END지시어전에 DEFINE_PTHIS 선언 44 | * GET_STRING - 유저로부터 문자열을 받고, DX의 버퍼크기만한 DS:DI버퍼로 작성된 문자열은 받는 프로시져. 'Enter'가 입력이 되면 멈춘다. 45 | * 사용법 : END 지시어전에 DEFINE_GET_STRING 선언 46 | * CLEAR_SCREEN - 화면을 깨끗이 정리하고(스크롤되는 전체화면), 커서를 위에 위치시키는 프로시져. 47 | * 사용법 : END 지시어 전에 DEFINE_CLEAR_SCREEN 선언 48 | * SCAN_NUM - 키보드로부터 정수를 입력받고, CX레지스터에 결과값을 저장하는 프로시져. 49 | * 사용법 : END 지시어 전에 DEFINE_SCAN_NUM 선언 50 | * PRINT_NUM - AX레지스터에 있는 정수를 출력하는 프로시져. 51 | * 사용법 : END 지시어 전에 DEFINE_PRINT_NUM and DEFINE_PRINT_NUM_UNS 선언 52 | * PRINT_NUM_UNS - AX레지스터에 있는 부호없는 정수를 출력하는 프로시져. 53 | * 사용법 : END지시어 전에 DEFINE_PRINT_NUM_UNS 선언 54 | 55 | 56 | 57 | 예제를 보시면 쉽게 사용법을 아실 수 있습니다. 함수이므로 call 명령으로 호출해야합니다. 매크로와는 다릅니다. 58 | ``` 59 | include 'emu8086.inc' 60 | 61 | ORG 100h 62 | 63 | LEA SI, msg1 ; ask for the number 64 | CALL print_string ; 65 | CALL scan_num ; get number in CX. 66 | 67 | MOV AX, CX ; copy the number to AX. 68 | 69 | ; print the following string: 70 | CALL pthis 71 | DB 13, 10, 'You have entered: ', 0 72 | 73 | CALL print_num ; print number in AX. 74 | 75 | RET ; return to operating system. 76 | 77 | msg1 DB 'Enter the number: ', 0 78 | 79 | DEFINE_SCAN_NUM 80 | DEFINE_PRINT_STRING 81 | DEFINE_PRINT_NUM 82 | DEFINE_PRINT_NUM_UNS ; required for print_num. 83 | DEFINE_PTHIS 84 | 85 | END ; directive to stop the compiler. 86 | ``` 87 | -------------------------------------------------------------------------------- /addsubmul.md: -------------------------------------------------------------------------------- 1 | #덧셈,뺄셈,곱셈 2 | 사칙연산은 기본이겠지요. add, sub, mul, imul, div, idiv, inc, dec, neg 명령들이 사칙연산과 관련있습니다. 3 | 4 | ``` 5 | org 100h 6 | 7 | mov ax, 3 8 | mov bx, 5 9 | 10 | add ax, 1 11 | sub bx, 1 12 | add ax, bx 13 | sub bx, ax 14 | 15 | ret 16 | ``` 17 | 18 | add와 sub는 설명이 필요없겠지요? 19 | 다음은 mul, imul을 실험해보겠습니다. 20 | ``` 21 | org 100h 22 | 23 | mov al, 90h 24 | mov bl, 90h 25 | mul bl 26 | 27 | mov ax, 1000h 28 | mov bx, 1000h 29 | mul bx 30 | 31 | mov ax, 20 32 | mov bx, -20 33 | imul bx 34 | 35 | mov ax, 0ffffh 36 | mov bx, 0ffffh 37 | imul bx 38 | 39 | mov ax, 0ffffh 40 | mov bx, 0ffffh 41 | mul bx 42 | 43 | 44 | ret 45 | ``` 46 | 먼저 알아야할 것이 있습니다. 하나의 레지스터의 크기는 16비트입니다. 그럼 레지스터끼리 곱했을 때 결과값인 몇 비트가 될까요? 47 | 48 | 100과 100을 곱하면 0이 2개 + 2개 해서 4개가 됩니다. 그럼 100h와 100h를 곱하면 어떻게 될까요? 마찬가지 입니다. 10000h가 됩니다. 즉 자리수가 두배가 되는 것이지요. 16비트끼리 곱하면 32비트가 됩니다. 무슨 말이냐면 16비트끼리 곱했을 때 32비트의 자리가 있어야 결과값을 저장할 수 있다는 것입니다. 안그러면 16비트끼리 곱하도록 허용할 필요가 없지요. 49 | 50 | 그래서 곱하기 연산에는 명령에는 안보이는 추가 레지스터를 사용합니다. 51 | 52 | 먼저 mul bl 이라는 명령은 실행해보면 bl이 8비트이므로 프로세서는 8비트끼리의 연산이라고 생각합니다. 그래서 16비트의 결과값을 저장하려고합니다. 곱하려는 수는 bl이고 곱해지는 수는 al입니다. al은 명령어에 없습니다. 그냥 프로세스가 무조건 곱하기에 al이 사용된다고 미리 정해버린겁니다. 그리고 결과값은 ax입니다. 16비트가 필요하므로 16비트 레지스터 ax 전체를 사용합니다. 90h와 90h를 곱하면 16진수로 5100입니다. 51이 ah에 00이 al에 쓰여있나요? 53 | 54 | 이전 글에서 ax 레지스터의 이름이 accumulate register 이고 계산에 사용되는 레지스터라고 소개했습니다. 이렇게 mul이나 div에서 ax 레지스터를 무조건 사용하기 때문에 계산에 특화된 레지스터라고 말씀드린 것입니다. 55 | 56 | 그 다음으로 mul bx 명령은 ax와 bx를 곱하는 명령입니다. ax와 bx를 곱하면 32비트의 결과값이 나오는데 ax는 16비트입니다. 나머지 16비트는 어디에 저장될까요? 흥미를 위해 답을 알려드리지 않겠습니다. 에물레이터로 실험해보면 어떤 레지스터에 100h값이 나타납니다. 1000h * 1000h는 1000000h입니다. 이 값을 16비트씩 끊어보면 상위 16비트는 100h이고 하위 16비트는 0000h입니다. 상위 16비트가 저장되는 레지스터에는 100h가 저장되고 하위 16비트가 저장되는 ax에 0000h가 저장됩니다. 57 | 58 | 설명이 좀 길고 주절주절했습니다만 mul명령에 대해 두가지만 기억하시면 됩니다. 8비트 연산을 때는 ax레지스터가 자동으로 사용된다는 것과 16비트 일때는 dx도 사용된다는 것입니다. 59 | 60 | 이제 imul을 알아보겠습니다. imul은 부호를 생각하는 곱셈입니다. mul은 부호를 고려하지않고 무조건 곱셈을 합니다. 하지만 imul은 부호를 고려합니다. 다시한번 말씀드리자면 가장 왼쪽 비트가 1이면 음수이고 0이면 양수입니다. 61 | 62 | 20과 -20을 곱해볼까요? 코드에는 -20이라고 써놨지만 컴퓨터는 -라는 부호를 모릅니다. 부호비트가 1인지만 생각합니다. 에물레이터에서 -20이 어떻게 나타나는지 보세요. 0ffech입니다. 20은 14h이지요. 0ffech + 14h는 0이니까 0ffech는 -20이 맞겠지요? imul bx의 결과값은 음수가 되어야합니다. 그리고 dx에도 결과값이 저장되야하지요. 그래서 dx 값이 뭔가요? 0ffffh이지요. 즉 결과값이 0fffffe70h이라는 것입니다. 부호비트가 1이지요. 따라서 음수라는 것입니다. 400이 16진수로 190h입니다. 0fffffe70h에 190h를 더해보세요. 100000000h이지요. 그런데 32비트라는 제약때문에 제일 왼쪽 1은 날아갑니다. 즉 0이되는 것이지요.그래서 0fffffe70h가 -400이 되는 것입니다. 63 | 64 | 0ffffh는 -1입니다. -1에 -1을 곱하면 1입니다. 1이 되나 해보세요. 만약에 0fffe0001라면 부호없는 곱하기가 되는 것이고 1이라면 부호있는 곱하기 입니다. dx 값이 0인지 0fffeh인지 확인해보시기 바랍니다. 65 | 66 | 마지막이 부호없는 0ffffh와 0ffffh의 곱셈입니다. -1과 -1의 곱셈과 다른지 확인해보세요. -------------------------------------------------------------------------------- /stack.md: -------------------------------------------------------------------------------- 1 | #임시 데이터를 보관하는 스택 2 | 3 | 스택은 임시 데이터를 보관하는 메모리 공간입니다. 다시 말씀드립니다. 임시 데이터를 보관하는 메모리입니다. 절대 오래동안 쓸 데이터를 보관할 메모리가 아닙니다. 오래동안 여기저기서 사용할 데이터는 변수에 저장해야합니다. 스택은 잠깐 사용할 데이터를 저장합니다. 4 | 5 | 스택을 알게되면 어셈블리 프로그래밍을 위해 필요한 명령어는 다 배운게 됩니다. 그만큼 조금 어렵기도 하고 복잡하게 사용할 수도 있으므로 나름대로 많이 실험해보시기 바랍니다. 6 | 7 | 스택을 사용하는 명령어는 push와 pop입니다. push는 스택에 16비트 값을 저장합니다. pop은 스택에서 16비트 값을 읽어옵니다. 8 | 9 | push의 사용법을 보면 레지스터 이름이 와도 되고 메모리 주소나 상수 값이 와도 됩니다. 레지스터의 값이나 메모리에 있는 값을 스택에 저장합니다. 10 | ``` 11 | push ax 12 | 13 | push ds 14 | 15 | push [bx] 16 | ``` 17 | pop 명령에는 레지스터와 메모리가 사용됩니다. 스택에서 값을 읽어서 레지스터나 메모리에 저장합니다. 18 | ``` 19 | pop ax 20 | 21 | pop ds 22 | 23 | pop [bx] 24 | ``` 25 | 어렵지 않아 보이지요? 이제 진짜 스택이 뭔지 알아봅시다. 26 | 27 | ##스택 포인터 28 | 29 | 스택은 메모리 공간이라고 했습니다. 그런데 메모리 어디라고는 설명을 안했습니다. 메모리 몇번지에 데이터를 저장하는 걸까요? 스택에 데이터를 저장했는데 메모리 몇번지에 저장되었는지 알 수 없을까요? 30 | 31 | 스택은 메모리 공간을 가르키는 말이므로 당연히 어디서부터 어디까지가 스택으로 사용되는 공간인지를 알아야합니다. 보통은 운영체제가 프로그램에게 일정한 크기의 공간을 스택으로 사용하도록 지정해줍니다. 그리고 그 공간을 다 쓰면 더 이상은 메모리를 쓰지 못하게 막습니다. 하나의 프로그램이 무한정 메모리를 사용할 수 없으니까요. 32 | 33 | 스택 공간의 주소는 ss (스택 세그먼트 레지스터), sp (스택 포인터 레지스터) 로 가르칩니다. 변수의 메모리 주소는 ds:[si]나 ds:[bx] 등으로 가르친다고 말씀드렸습니다. 스택도 마찬가지로 세그먼트 레지스터와 포인터 레지스터로 메모리 주소를 표현하지만 스택 전용의 레지스터가 따로 있다는게 다릅니다. 그만큼 자주 사용되니까 따로 레지스터를 만들어 둔 것이겠지요. 운영체제는 프로그램이 실행될 때 ss:sp를 설정해줍니다. 그리고 프로그램은 운영체제가 지정해준 메모리 위치를 스택으로 사용합니다. 34 | 35 | push 명령이 스택에 데이터를 저장한다고 말씀드렸지요. 좀더 세부적으로 말씀드리면 36 | 37 | 1. sp 레지스터에서 2를 뺀다(16비트이므로 하나의 데이터가 2바이트입니다. 그래서 2를 뺍니다) 38 | 1. 저장할 데이터를 ss:[sp] 주소에 기록한다. 39 | 40 | pop 명령은 반대로 동작합니다. 41 | 42 | 1. ss:[sp] 주소에서 데이터를 읽어온다. 43 | 1. sp 레지스터에 2를 더한다. 44 | 45 | 보통 데이터는 메모리 주소가 작은 지점에 저장하고 메모리 주소가 큰 쪽으로 데이터를 저장합니다. 100h에 문자열 "hello"를 저장한다면 h를 100h에 저장하고 e를 101h에 l를 102h에 저장합니다. 메모리 번지가 커지면서 다음 데이터가 저장됩니다. 46 | 47 | 하지만 스택은 반대입니다. 스택을 사용하기위해 push를 할 때마다 sp의 값이 줄어듭니다. 다음 데이터의 메모리 주소가 작아집니다. 스택 레지스터 sp의 값이 0fffeh이라면 처음 데이터를 저장할 때 먼저 sp 값을 0fffch로 감소시킵니다. 그리고 [0fffch]에 데이터를 저장합니다. 에물레이터를 실행하고 stack 버튼을 누르시면 스택의 상태를 확인할 수 있습니다. 48 | 49 | 스택의 주소값이 작아진다는 이유때문에 저장한 데이터가 거꾸로 꺼내진다는 특징이 있습니다. 1,2,3,4,5를 저장한다면 5,4,3,2,1 순서로 데이터가 꺼내집니다. 스택이란 말의 원래 뜻이 접시를 쌓아놓는 것이라고 합니다. 가장 먼저 쌓아놓은 접시는 가장 나중에 꺼낼 수가 있습니다. 가장 나중에 쌓은 접시가 가장 위에 있으니까 가장 먼저 꺼내집니다. 50 | 51 | 다음 예제를 한번 실행해보겠습니다. 52 | ``` 53 | ORG 100h 54 | 55 | 56 | mov ax, 1234h 57 | push ax 58 | push ax 59 | pop bx 60 | pop dx 61 | 62 | ret 63 | ``` 64 | 다음은 에물레이터의 실행 결과입니다. 65 | 66 | 67 | ![](/assets/2537.png) 68 | 69 | 에물레이터를 처음 실행해서 stack 버튼을 누릅니다. 그러면 ss:sp 주소에 맞게 700h:0fffeh 번지의 메모리에 어떤 값들이 있는지 나타납니다. 처음에는 모두 0입니다. push ax를 실행하면 0fffch에 1234h가 저장되고 그 다음 push ax를 실행하면 0fffah에 1234h가 저장됩니다. 처음 프로그램이 실행될때의 sp 값은 0fffeh인데 0fffeh에는 데이터가 저장되지 않습니다. 70 | 71 | 그리고 pop bx를 실행하면 sp 값이 0fffah에서 0fffch가 되면서 bx에 스택에 있는 값 1234h가 저장됩니다. pop dx도 마찬가지입니다. 72 | 73 | 메모리에 있는 값들을 레지스터로 복사했지만 메모리의 값을 바꾸지는 않았으므로 메모리값들은 그대로 남아있습니다. 이렇게 스택에 값이 남아있기때문에 해커들이 스택의 값을 읽어서 해킹에 사용하는 일이 있었습니다. 74 | 75 | -------------------------------------------------------------------------------- /repeat.md: -------------------------------------------------------------------------------- 1 | #여러번 반복하기 2 | 3 | 4 | 혹시 여기까지 읽고계신 분들이 계시다면 재미가 있으신지 궁금합니다. 혹시 재미가 있으시다면 잠깐 한번 C 코드나 자바 코드를 생각해보세요. 아무 코드나 한번 보셔도 좋습니다. 변수를 만들거나 함수를 호출하거나 루프를 만들거나 하는 일들이 어떻게 보이시나요? 왠지 그 속에 있는 어셈블리 코드가 보이는 것 같은 느낌이 들지 않으시나요? 분명히 말씀드릴 수 있는 것은 어셈블리를 조금만 공부해보시면 언어에 상관없이 더 좋은 코드를 만드실 수 있다는 것입니다. 여기서 좋다는건 사람에게 좋은 읽기 좋은 코드가 아니라 기계에게 좋은 빠른 코드를 말합니다. 읽기 좋은 코드는 그 언어를 잘 알아야 만들 수 있는것 같습니다. 어려운 문제이지요. 빠른 코드는 보다 명확합니다. 어셈블리 코드로 길이가 짧으면 빠릅니다. 루프가 적으면 빠르지요. 루프의 횟수는 내가 정하는게 아니라 필요에 따라 정해지는 것이므로 내가 잘 할 수 있는게 아닙니다. 결국 코드가 어떻게 어셈블될지 안다는게 좀더 짧은 어셈블리 코드로 컴파일되는 코드를 만드는데 도움이 되고 프로그램의 성능을 높일 수 있게 됩니다. 재미로 호기심으로 시작한 어셈블리 프로그래밍이지만 한가지 효과는 얻었네요. 5 | 6 | 한가지 어셈블리와 전혀 상관없는 언어가 있습니다. 어셈블리를 아무리 잘해도 더 좋은 코드를 만들 수 없고 컴퓨터 구조와도 아무 상관없는 언어가 있습니다. 대표적으로 LISP계역의 언어가 있습니다. HTML도 그렇지요. 특히 LISP 언어는 컴퓨터 자체에 대한 지식보다는 컴퓨터 과학적인 알고리즘이나 논리에 대한 지식이 있어야 잘 할 수 있는 언어입니다. 어셈블리와는 다른 매력이 있습니다. 7 | 8 | 잡소리는 그만하고 반복을 어떻게하는지 알아보겠습니다. 9 | 10 | 반복을 위해서 loop라는 명령어가 있습니다. 사실 점프와 다를게 없습니다. 비교하고 반복하는 것입니다. 단지 cmp, jmp 명령을 하나로 만든 명령입니다. 반복이라는 것은 몇번 반복하겠다는 조건이 깔려있는 것입니다. 이 몇번을 확인해주는 기능이 들어있는게 loop 명령입니다. cmp 를 생략해도 된다는 것입니다. cx 레지스터에 반복할 횟수를 저장해놓으면 자동으로 cx 레지스터의 값을 1씩 줄이다가 0이 되면 루프가 끝나는 것입니다. 11 | 12 | ``` 13 | include "emu8086.inc" 14 | 15 | org 100h 16 | 17 | mov al, 0 18 | mov bl, 10 19 | 20 | x_loop: 21 | cmp al, bl 22 | je equal 23 | putc 'x' 24 | inc al 25 | jmp x_loop 26 | 27 | equal: 28 | mov cx, 10 29 | y_loop: 30 | putc 'y' 31 | loop y_loop 32 | 33 | ret 34 | ``` 35 | 루프를 2가지 방식으로 만들어봤습니다. cmp와 jmp를 이용하는 방식과 loop를 이용하는 방식의 차이를 보면 왜 loop라는 명령을 따로 만들었는지 알 수 있습니다. cmp와 jmp를 가지고도 할 수 있는 일을 왜 따로 명령을 만들었을 까요? 2가지 이유가 있습니다. 36 | 37 | 첫번째는 프로그램의 길이입니다. loop를 쓰면 프로그램의 길이가 줄어듭니다. 프로그램이 짧다는 것은 빠르다는 것 뿐만 아니라 프로그램을 저장할 저장소 즉 디스크나 메모리의 사용이 줄어든다는 것입니다. 지금이야 프로그램을 짧게 만다는게 특수한 분야에서만 신경쓰는 일이되었지만 현업에서 어셈블리로 프로그래밍하던 까마득한 옛날에는 메모리가 엄청난 돈이었습니다. 8086에 얼마의 메모리를 달 수 있다고 말씀드렸나요? 1MB입니다. 컴퓨터의 메모리가 1MB가 한계이고 실제로 1MB의 메모리를 달려면 수백만원을 써야하던 시절입니다. 도스 시절까지만 해도 몇백 바이트의 메모리가 없어서 프로그램이 실행되지 못하는게 부지기수였습니다. 따라서 프로그램이 짧다는게 얼마나 중요한 기술이었을지 알 수 있습니다. 그때는 플로피디스크를 쓰던 시대였으므로 프로그램이 크면 디스크를 읽는 시간도 길어지므로 실행이 된다해도 더 오래 기다려야한다는 문제도 있습니다. 아마 모르시는 분들이 더 많으시겠지만 디스크를 더 오래 읽는 다는 것은 디스크의 수명이 줄어든다는 것입니다. 플로피디스크라는 것이 얼마나 말도 안되게 보관도 어렵고 수명이 짧았는지 상상도 못하실 겁니다. 까마득한 옛날에는 프로그램이 짧은게 좋았다는 것입니다. 38 | 39 | 두번째 이유는 레지스터의 사용을 줄이는 것입니다. cmp와 jmp를 쓰려면 몇번 실행했는지를 저장하는 레지스터와 몇번까지 실행해야할지를 저장하는 레지스터 2개가 필요합니다. 하지만 loop를 쓰면 cx 레지스터 하나만 쓰면 됩니다. 하나 차이가 얼마나 하길래라고 생각하시는 분들이 계실겁니다. 하지만 8086에 몇개의 레지스터가 있는지 생각해보세요. 우리가 막 쓸 수 있는 레지스터는 ax, bx, cx, dx, si, di 정도이므로 6개입니다. 빠르게 접근할 수 있는 레지스터가 6개인데 그중에 하나라면 얼마나 중요할까요. 그때나 지금이나 메모리 속도와 레지스터 속도는 수백만배 차이가 납니다. 지금도 최대한 많은 레지스터를 사용해서 메모리에 접근하지 않는게 컴파일러의 중요한 기술중에 하나입니다. 조금이라도 더 레지스터를 늘리기 위해서 인텔과 ARM이 얼마나 많은 노력을 하는지 모릅니다. 레지스터의 사용을 줄이는 것도 중요하나는 말이지요. 레지스터 접근이 빠르므로 성능과도 연관이 있습니다. 40 | 41 | 성능성능 지겹게 성능 이야기를 하고있습니다. 그만큼 8086의 시절에는 컴퓨터가 느렸고 조금이라도 성능을 쥐어짜기위해 가독성이니 개발 환경이니 하는 사람중심적인 고민은 사치로 생각하던 시절입니다.(주1) 42 | 43 | --- 44 | 주1. 그런데 재밌는건 맨먼스 미신이나 프로그래밍 심리학같은 진짜 개발자 중심적인 생각이 중요하다고 말하는 책들이 이런 시절에 쓰여졌다는 것입니다. 그리고 더 웃기는건 예나 지금이나 제품 개발에서 사람은 자원으로만 생각되고 있다는 것이지요. -------------------------------------------------------------------------------- /logicoperation.md: -------------------------------------------------------------------------------- 1 | #참과 거짓을 구별할때 쓰는 논리 연산 2 | 3 | 컴퓨터로 판단해야할 때가 많습니다. 사용자가 마우스 클릭을 했는지 안했는지 키보드를 눌렀는지 안눌렀는지 엑셀에서 셀들의 합이 100을 넘는지 안넘는지 하나의 프로그램에 얼마나 많은 판단이 들어가는지 모릅니다. 4 | 5 | 컴퓨터로 하는 판단을 논리 연산이라고 부릅니다. logic operation이라고 하는데 어떤 연산 결과가 내가 생각하는 결과와 같냐 다르냐를 판단하는 연산입니다. 루프를 예로 들면 10번 반복해서 화면에 'A'를 출력하고 싶을 때 현재 몇번 출력했는지를 기억해놨다가 10번이 되었는지 안되었는지를 판단해야합니다. 이럴때 횟수와 10을 비교해서 횟수가 10보다 작은지를 판단합니다. 이런게 논리 연산입니다. 6 | 7 | ##플래그 레지스터 8 | 9 | 플래그 레지스터라는 것이 있습니다. 에물레이터를 실행해보면 아래쪽에 있는 버튼들 중에 가장 오른쪽에 flags라는 버튼이 있습니다. 이 버튼을 누르면 현재 플래그 레지스터의 상태를 보여줍니다. 10 | 11 | ![](/assets/cpu.gif) 12 | 13 | 이 그림이 생각나시나요? 오른쪽 아래에 이름은 안써있지만 Overflow, Direction 등등 16개의 사각형이 있습니다. 이게 플래그 레지스터입니다. 14 | 15 | 이 레지스터는 16비트인데 각 비트마다 어떤 의미를 가집니다. 각각의 비트에 대해서 설명하겠습니다. 비트니까 당연히 1아니면 0이겠지요? 1은 해당하는 상태가 맞다는 의미이고 0은 해당 상태가 아니라는 것입니다. 16 | 17 | Carry Flag (CF) - 부호없는 정수가 overflow일경우 플래그값은 1로 설정됩니다. 무슨 말이냐면 al에 255를 넣고 1을 더하는 연산을 하는 경우에 결과값이 범위가 8비트 범위 0 ~ 255를 넘게되고 이런 상태를 overflow라고 부릅니다. overflow가 아닌경우에는 플래그 값은 0으로 남아있습니다. 18 | 19 | Zero Flag (ZF) - 결과값이 0일때 1로 설정됩니다. 같은 레지스터끼리 아니면 같은 값을 가진 레지스터끼리 빼기를 하면 0이 되지요. 결과값이 0이 아닐경우 플래그 값은 0이됩니다. 20 | 21 | Sign Flag (SF) - 결과값이 음수일경우 1이 됩니다. 양수일때는 0이 되고요. 22 | 23 | Overflow Flag (OF) - 부호 있는 정수끼리의 연산에서 overflow일경우 1이 됩니다. 예를들어 100 + 50할 경우입니다.(결과값 범위가 -128...127이 아니므로) Carry Flag와는 다릅니다. 24 | 25 | Parity Flag (PF) - 비트값이 1인것이 짝수이면 1로 설정하고, 홀수이면 0으로 설정한다. 결과값이 word이고, 8 low bit이어도 분석을 한다. 26 | 27 | Auxiliary Flag (AF) - 부호 없는 정수 nibble(4 비트)이 overflow일경우 1로 설정한다. 28 | 29 | Interrupt enable Flag (IF) - CPU가 외부 장치에 의해 인터럽트를 반응할때 1로 설정한다. 30 | 31 | Direction Flag (DF) - 이 플래그는 명령어에 의해 data chain이 처리 될때 사용된다. 프로세싱이 메모리 앞 번지에서 뒷번지로 진행될때는 0로 설정되고 프로세싱이 메모리 뒷번지에서 앞번지로 진행 될때는 1로 설정된다. 32 | 33 | 34 | 35 | ``` 36 | org 100h 37 | mov al, 100 38 | mov bl, 50 39 | add al, bl 40 | 41 | mov al, 255 42 | mov bl, 1 43 | add al, bl 44 | ret 45 | ``` 46 | 47 | 48 | 100과 50을 더했습니다. 그런데 100은 al 레지스터에 있고 50은 bl 레지스터에 있네요. 각각 8비트 레지스터입니다. 따라서 8비트끼리의 더하기이므로 150이란 결과값에 overflow 상태가 될까요 carry 상태가 될까요? 레지스터 전체의 범위를 벗어나지 못했으니 carry가 없고 overflow만 있는 것입니다. carry란 가능한 범위를 벗어나는 것을 말합니다. 49 | 50 | 예제를 실행하면서 어떤 플래그 비트들이 1로 바뀌는지 확인해보세요. 51 | 52 | ##논리 연산 명령어 53 | 54 | 논리 연산을 실행하는 CMP, AND, TEST, OR, XOR, NOT 명령어들을 하나씩 실험해보겠습니다. 55 | 56 | and, or, xor, not 명령어는 이름 그대로의 연산을 합니다. 예제를 실행해볼까요. 57 | ``` 58 | org 100h 59 | 60 | mov ax, 1 61 | mov bx, 0 62 | and ax, bx 63 | 64 | mov ax, 1 65 | mov bx, 0 66 | or ax, bx 67 | 68 | mov ax, 1 69 | mov bx, 0 70 | xor ax, bx 71 | 72 | mov ax, 1 73 | mov bx, 0 74 | not ax 75 | not bx 76 | 77 | mov ax, 1 78 | test ax, ax 79 | test ax, 1 80 | 81 | ret 82 | ``` 83 | 1 and 0, 1 or 0, 1 xor 0, not 1, not 0 연산을 한번씩 실험해보는 소스입니다. 84 | 85 | 1 and 0의 결과는 뭔가요? and 연산이라는 것은 두가지 모두 참이어야 결과가 참이되고 하나라도 0이면 거짓인 연산입니다. 따라서 1 and 0은 0이지요. 실험을 하면서 잘 봐야할 것은 레지스터 값이 어떻게 바뀌느냐와 어떤 플래그가 바뀌느냐 입니다. ax가 1이고 bx가 0인 상황에서 and ax, bx 명령의 결과값인 0이 어디에 저장되나요? ax입니다. ax의 값이 1에서 0으로 바뀌었습니다. 그리고 zero 플래그가 1로 바뀌었습니다. 연산 결과가 0이니까요. 그 외에 overflow나 carry는 변하지 않습니다. and ax, bx를 and bx, ax로 바꿔서 해보세요. 결과값이 어디에 저장될까요? and ax, bx를 실행할 때와 상태 플래그가 같나요? 86 | 87 | test 명령은 약간 특이한 명령입니다. 하는 일은 and와 동일한데 차이는 레지스터의 값을 바꾸지 않습니다. 상태 레지스터만 바꿉니다. 그래서 test입니다. 값이 같은지 0이 아닌지를 테스트하는 것입니다. test ax, ax 이렇게 같은 레지스터를 테스트하면 레지스터의 값이 0인지를 확인하겠다는 것이고, test ax, 1 로 실행하면 ax 레지스터의 값이 1인지 0인지를 확인하는 것입니다. test ax, 1 명령이 실행되고 zf가 1이되면 (ax and 1)의 결과값이 0이라는 뜻이니까 ax가 0이겠지요? 88 | 89 | 흥미유발?을 위해서 다른 명령어에 대한 설명은 하지 않겠습니다. 직접 해보세요. 문서로 백장 천장 쓰고 읽어봐야 한번 실행해서 이것저것 실험해보는것만 못합니다. 90 | 91 | -------------------------------------------------------------------------------- /memoryreadwrite.md: -------------------------------------------------------------------------------- 1 | #메모리 읽고 쓰기 2 | 3 | 데이터를 레지스터에 넣어봤습니다. 그런데 데이터가 여러개라면 어떻게 해야될까요? 주소록을 만들거나 수백명 학생들의 학점 관리를 해야하는데 몇개뿐인 레지스터를 가지고 처리할 수 없겠지요. 그래서 메모리에 데이터를 쓰고 읽고 해야합니다. 4 | 5 | 보통의 프로그래밍 언어에서는 변수를 하나의 이름으로 생각합니다. C에서는 int a; 와 같이 a라는 이름으로 변수를 지정합니다. 그런데 컴퓨터에게는 변수란게 없습니다. 변수는 사람이 기억하기 쉽도록 이름을 지어놓은 것입니다. 변수란 사실은 메모리의 특정한 위치를 말합니다. C에서는 포인터라는 개념을 배우는데요 이게 메모리 주소를 말합니다. 어셈블리 언어에서는 변수없이 포인터로만 데이터를 사용할 수 있어야합니다. 어셈블러에서는 변수 이름을 만들 수 있도록 해주는데 사실은 눈속임입니다. 그냥 특정 메모리 주소를 변수 이름으로 바꿀 뿐입니다. 6 | 7 | 다시한번 말씀드리겠습니다. 변수는 눈속임입니다. 컴퓨터는 이름을 모릅니다. a가 뭔지 모릅니다. 컴퓨터가 이해할 수 있는 것은 주소입니다. 주소는 숫자이니까 이해할 수 있습니다. (왜 수가 첫번째 글이 되었는지 납득이 되시나요? 모든게 수입니다!!) 8 | 9 | 실습을 해볼까요? 쓸데없이 복잡합니다만 다 이해가 안되도 상관없습니다. 메모리 주소를 표현하는 방법만 생각하세요. 10 | 11 | ``` 12 | org 100h 13 | mov ax, 0b800h 14 | mov ds, ax 15 | mov cl, 'A' 16 | mov ch, 11011111b 17 | mov bx, 15eh 18 | mov [bx], cx 19 | ret 20 | ``` 21 | 22 | 23 | 메모장을 실행해서 이 코드를 입력하고 var.txt 라는 이름으로 저장한 후 emu8086.exe var.txt를 실행합니다. 24 | 25 | 그리고 에물레이터 화면의 밑부분에 있는 screen이라는 버튼을 누릅니다. 그럼 빈 화면의 윈도우가 나타나지요. 그리고 single step 버튼으로 프로그램을 ret까지 실행해봅니다. screen 화면에 A라는 핑크색 글자가 나타났나요? 26 | 27 | 소스를 설명하겠습니다. 28 | 29 | 처음에 org 100h는 죄송하지만 설명하지 않겠습니다. org 100h은 그냥 프로그램 처음에 무조건 넣는 걸로 하겠습니다. 굳이 간단하게 설명하자면 에물레이터를 처음 실행하면 ip가 100h인데 그렇게 프로그램이 100h 위치에서 실행한다는 것을 의미합니다. 왜 100h가 필요한지 왜 다른 값이 아니라 100h인지 등등 설명할게 많은데 사실 알아도 지금은 전혀 쓸모없는 지식입니다. 8086에서만 해당되는 내용이고 요즘 프로세서와는 상관없는 내용이므로 그냥 그렇게 쓰겠습니다. 30 | 31 | 그 다음이 0b800h라는 주소를 ds에 저장합니다. 0b800h라는 값을 곧바로 ds 레지스터같은 세그먼트 레지스터에 곧바로 저장할 수는 없습니다. 8086 프로세서의 하드웨어적인 제약사항입니다. 그냥 프로세서가 그렇게 못하도록 만들어진 것입니다. 그래서 값을 바로 쓸 수 있는 범용 레지스터에 먼저 저장한 후에 레지스터의 값을 ds에 복사한 것입니다. 32 | 33 | 그리고 ds가 또 나타나는 지점은 mov ds:[bx], cx입니다. 여기의 ds:[bx]라는 표현이 바로 메모리의 위치를 나타내는 표현입니다. 이전 글에서 메모리 주소는 세그먼트 주소와 범용 레지스터를 결합해서 나타낸다고 말씀드렸습니다. ds에는 0b800h 값이 있고 bx에는 15eh 값이 있으므로 ds:[bx]는 0b8000h + 15eh라는 주소를 말합니다. 그럼 [] 표시는 뭘까요? mov bx, cx는 bx 레지스터에 cx레지스터의 값을 복사하라는 명령입니다. bx에 메모리 주소가 있고 bx에 저장된 메모리 위치에 cx 값을 넣으라는 명령을 내리고 싶을 때는 bx에 []를 붙입니다. 그래서 결국 mov [bx], cx 혹은 mov ds:[bx], cx 가 됩니다. ds는 생략이 가능합니다. 프로세서가 알아서 메모리 주소를 계산할 때 ds를 읽습니다.(주1) mov [bx], cx로 바꿔서 실행해보세요. 결과는 같습니다. 34 | 35 | 그리고 cl에 'A'를 씁니다. 이건 화면에 출력할 문자입니다. '1'을 써도 되고 'a'를 써도 됩니다. 작은 따옴표는 그 문자의 아스키코드를 말하는데 아스키코드라는 것을 아신다면 이해하시면 되고 아니라면 그냥 'A'는 A라는 문자 자체를 의미한다고 생각하시면 됩니다. 36 | 37 | ch에는 이상한 값을 쓰는데 이것은 그냥 바탕은 핑크색 글자는 흰색이라는 것을 의미합니다. 38 | 39 | bx는 화면에서 어느 위치를 말합니다. 혹시 옛날 386AT 컴퓨터가 기억나시나요? 흑백 화면이나 녹색 화면일때도 있었고 브라운관 모니터였지요. 그때 화면은 지금의 윈도우처럼 그래픽이 출력되는 화면이 아니었습니다. 가로 80글자 세로 25글자만 출력할수 있었습니다. 그게 바로 지금 보시는 screen 화면입니다. 옛날의 화면 출력을 에물레이트해준 것이지요. 40 | 41 | 옛날 화면에 하나의 문자를 출력할때는 2바이트의 값을 써야합니다. 화면 한 줄이 80개의 문자를 출력할 수 있으니까 한 줄당 160바이트입니다. 350은 몇번째 줄일까요? 첫번째 줄은 0,2,4 ... 158, 두번째 줄은 160,162,164 ... 318, 세번째 줄이 320... 이므로 350이라는 값은 세번째 줄에 표시한다는 것입니다. 42 | 43 | 세번째 줄에서 몇번째 칸일까요? 350-320은 30인데 한 문자당 2바이트입니다. 30은 15번째 위치를 말합니다. 44 | 45 | 이해가 안되고 어렵다고 생각하시는게 당연합니다. 얼마나 많은 사람들이 C언어의 포인터 개념을 이해하지 못해서 컴퓨터 공학을 포기하고 전자/전기 공학으로 전공을 바꾸는지 아신다면 위안이 되실겁니다. 태어나서 이런 식으로 수학을 사용해본 일이 없으니 당연한 일입니다. 그냥 ds:[bx]만 써넣으면 재미가 없을것 같아서 스크린에 글자 출력하는 소스도 넣어봤습니다. 이해가 안되시면 그냥 넘기세요. ds:[bx]만 알면 됩니다. 46 | 47 | 우리가 여기에서 배워야할 것은 화면에 몇번째 위치냐가 아니고 메모리의 특정 위치를 지정하는 방법입니다. 다시한번 말씀드리면 ds:bx가 메모리 주소이고 레지스터 값을 읽는게 아니라 레지스터 값을 메모리 주소로 인식해서 메모리에 접근하라는 표현이 ds:[bx]입니다. 48 | 49 | 마지막에 있는 ret 명령어는 프로그램을 끝내는 명령입니다. 50 | 51 | --- 52 | 53 | 주1: 메모리 주소가 저장된 레지스터라니 뭔가가 생각나지 않으세요? bx 레지스터가 바로 C언어에서 포인터의 역할을 하는 것입니다. 포인터는 그냥 읽으면 정수값이지요. 그런데 *를 붙이면 그 값을 메모리 주소로 인식해서 메모리에 접근하지요. C에서의 * 연산자가 어셈블리 언어에서 []가 되었습니다. 정확하게는 반대로 []가 C언어에서 *로 계승된 것입니다. 그거 아세요? C언어는 어셈블리 언어보다는 조금 쉬우면서 비슷하게 강력하고 자유로운 언어를 만들기위해서 생겨났습니다. 그래서 앞으로 어셈블리 언어를 공부하다보면 C언어가 다르게 보일 것입니다. 인간 컴파일러가 되서 C언어 코드를 보면 어떤 어셈블리 명령어에 해당되는지가 보일 것입니다. 매트릭스의 네오가 되는 것이지요. 54 | -------------------------------------------------------------------------------- /jump.md: -------------------------------------------------------------------------------- 1 | #점프와 함수 2 | 3 | 우리는 보통 프로그래밍을 할 때 함수를 만들어서 필요할 때마다 call이라는걸 합니다. 그런데 프로세서는 함수라는게 있다는걸 알까요? 당연히 모르니까 제가 이런 질문을 던졌겠지요. 4 | 5 | 우리는 변수를 이름으로 가르킵니다. int aaa라는 것은 int 타입이고 aaa라는 이름을 가진 저장소를 만들으라고 컴파일러에게 지시를 하는 문장입니다. 그럼 컴파일러는 int 타입이 4바이트인지 8바이트인지 판단하고 메모리 위치 어디가 적당할지 판단해서 적당한 장소에 몇 바이트정도 메모리를 예약합니다. 그리고 aaa라는 이름을 만날 때마다 그 메모리 위치에 값을 쓰거나 읽습니다. 6 | 7 | 어디가 적당한 위치인지 크기가 얼마가 적당할지는 프로세서가 32비트 모드인지 64비트 모드인지 운영체제가 뭔지 등등의 환경들을 종합해서 결정합니다. 컴파일러는 그냥 대략적인 위치만 지정하고 링커/로더가 실제 어떤 주소에 할당될지를 결정합니다. 세부적인 내용은 이런 내용이 필요한 분야에서 일하게 될 경우에는 필요하겠지만 일단 취미로 하는 단계에서는 그냥 컴파일러/링커/로더가 이런 일들을 한다고만 아시면 됩니다. 8 | 9 | 그리고 메모리 예약이라는 것도 좀 생각해볼만 합니다. 예약이라는게 뭘까요. 예약이라는 말은 내꺼라고 선언하고 남들은 못가지게 만드는 것입니다. 컴파일러는 메모리를 어떻게 예약할까요? 어딘가에 메모리 몇번지부터 몇번지까지는 누구꺼 이렇게 기록할까요? 기억이 나실지 모르겠지만 변수를 이야기할때 어셈블러는 변수를 선언한 그 위치를 그대로 변수 이름과 바꿔치기한다고 했습니다. 변수를 org 1000h 바로 다음에 선언했다면 변수 이름은 [1000h]로 치환됩니다. 10 | 11 | 우리처럼 어셈블러를 쓰는게 아니라 컴파일러는 쓰는 고급언어에서는 로컬 변수니 전역 변수니 다양한 변수들이 있습니다. 그렇게 다양한 기능을 제공하니까 고급언어이지요. 컴파일러는 이런 변수들의 타입에 따라 같은 타입의 변수들끼리 묶어서 저장합니다. a,b,c 세개의 변수가 있다면 a를 1000h에 b를 10004h에 c를 10008h에 저장하는 것이지요. 12 | 13 | 물론 정확하게 이야기하려면 이런 글 몇개로는 안될정도로 자세한 설명이 필요합니다. 그런게 시스템프로그래밍이나 컴파일러 등의 과목에서 배우는 것들입니다. 컴퓨터 공학/과학이라는 학문에서 뭘 배우는가를 잠깐 이야기하고 싶어서 말씀드렸습니다. 많은 분들이 고급언어를 다루시면서 컴퓨터학과를 전공한 애들은 뭘하는건가 하고 생각하신다고 합니다. 어셈블리를 공부하다보면 고급언어와 다르게 직접 해야할 일들이 어마어마하게 많습니다. 그래서 어셈블리로는 대규모 제품을 개발하지 못하는 것입니다. 원래는 어셈블리로 해야할 것들을 고급언어로 할 수 있도록 해주는게 컴파일러나 링커같은 개발툴이고 운영체제의 기능입니다. 그것들을 만드는게 컴퓨터를 전공한 사람들의 일이지요. 이런 툴들을 이용해서 개발하는 것은 컴퓨터를 전공할 필요가 없습니다. 대신에 세무서에서 쓰는 프로그램을 만드려면 세무관련 지식이 필요하고 포토샵을 만드려면 이미지처리나 수학에 대한 지식이 필요한데 이런 개발을 하는 데에는 적당한 컴퓨터 지식과 또 적당한 타분야 지식이 필요합니다. 이런 일들은 전공에 불문하고 할 수가 있지요. 컴퓨터 전공자가 해도 되고 경제학 전공자, 수학자 상관없습니다. 컴퓨터를 전공하지 않은게 더 유리할 수 있지요. 만약에 자신이 컴퓨터 전공이 아닌데 소프트웨어 개발을 하고 싶으시다면 자신의 전공 분야에서 사용하는 소프트웨어 개발에 참여해보세요. 돈도 성공도 얻을 확률이 높아집니다. 14 | 15 | 함수 이름을 설명하려다가 갑자기 전공이야기까지 나왔네요. 지울까하다가 그냥 남깁니다. 16 | 17 | 함수 이름이라는 것도 변수 이름과 마찬가지 입니다. 이름이라는게 사실은 메모리 주소라는것입니다. 함수 이름도 메모리 주소입니다. {}같은 특별한 표시로 묶어놓은 코드 모음을 함수라고 이름짓고 사용하지만 사실 기계가 이해하는 것은 어떤 표시도 아닌 주소입니다. 단지 그 주소에 내가 하려는 일을 위한 명령들이 있는 것이지요. 사람을 이름으로 부르지않고 주민번호로 부르는것 같은 차이일까요? 18 | 19 | 고급언어에서도 함수를 어셈블리로 변환할때 프로세서나 운영체제에 따라서 어떤 함수를 불러야할지 함수가 끝나면 어떻게 되돌아가야할지, 인자는 어떻게 전달해야하는지 등등 세부적으로 결정해야할 사항들이 매우 많습니다. 마찬가지로 이런 일들을 컴파일러가 하는 것이지요. 20 | 21 | 그럼 가장 간단한 점프부터 이야기해보겠습니다. 22 | 23 | ##무조건 점프 24 | 25 | 그냥 어딘가로 점프하는 것입니다. 당연히 그 어딘가는 주소 값이겠지요. 프로세서는 숫자만 인식하고 주소가 숫자니까요. 그런데 문제가 있습니다. 수십바이트부터 수백 수천 바이트 그 이상일때가 많은 프로그램에서 어떤 지점으로 점프하고 싶은데 그 주소를 알려면 어떻게 해야할까요? 명령어 갯수를 세야할지도 모릅니다. 이런 불편함을 어셈블러가 해소해줍니다. 어셈블러가 단순히 영여로된 명령어를 숫자로 바꾸는 일만 하는게 아닌 것이지요. 26 | 27 | 여기서 label이라는 개념이 나옵니다. 메모리의 특정 지점에 이름을 붙이는 것입니다. 28 | 29 | 30 | 31 | 32 | ``` 33 | org 100h 34 | mov ax, 0 35 | mov bx, 0 36 | start: 37 | jmp loop 38 | inc ax 39 | loop: 40 | inc bx 41 | jmp start 42 | ret 43 | ``` 44 | 45 | jmp 명령의 사용법과 label을 어떻게 사용하는지 아시겠지요? 이름: 이렇게 쓰면 어셈블러가 해당 위치의 주소를 기억했다가 jmp 등에서 이름을 주소로 바꿔줍니다. 46 | 47 | C언어에서 goto 명령이 jmp 명령을 그대로 표현한 것입니다. 예전에는 goto 명령을 절대 쓰면 안된다고 가르쳤었는데 요즘 트렌드는 에러 처리를 위해 goto 명령을 쓰라고 합니다. 프로세서가 발전해서 예전에는 goto 명령이 성능을 떨어지게했지만 요즘에는 goto 명령을 많이 써도 성능 저하가 없어서 그렇습니다. goto를 잘쓰면 코드 이해하기도 쉽고 에러 처리를 한곳에 모아놓아서 좀더 에러에 잘 대처할 수 있게됩니다. 48 | 49 | 예제를 하나 더 보겠습니다. 50 | ``` 51 | org 100h 52 | 53 | mov ax, 5 ; set ax to 5. 54 | mov bx, 2 ; set bx to 2. 55 | 56 | jmp calc ; go to 'calc'. 57 | 58 | back: jmp stop ; go to 'stop'. 59 | 60 | calc: 61 | add ax, bx ; add bx to ax. 62 | jmp back ; go 'back'. 63 | 64 | stop: 65 | 66 | ret ; return to operating system. 67 | ``` 68 | 69 | 8086에서는 점프할 수 있는 범위에 제한이 있습니다. 65,535바이트 이내로만 점프할 수 있습니다. 70 | 71 | 왜 그러냐면 점프하는 주소가 CS:[label] 이기 때문입니다. 변수에서 ds, es를 이용해서 주소를 20비트로 확장하는걸 배웠습니다. 그런데 점프는 16비트 65535까지만 됩니다. 왜냐면 CS의 값을 바꿀 수 없기 때문입니다. 그래서 그냥 레지스터 값의 범위인 16비트 범위로만 점프가 가능합니다. 72 | 73 | CS값을 한번 바꿔보세요. 되나요? 어떤 에러가 나나요? 요즘 환경에서는 필요없는 개념이라 더는 설명하지 않겠습니다. -------------------------------------------------------------------------------- /variable.md: -------------------------------------------------------------------------------- 1 | #변수 2 | 3 | 변수는 그냥 메모리의 위치입니다. 그런데 메모리의 위치는 숫자이고 숫자는 사람이 기억하기에 어렵지요. 그래서 어셈블러에서도 변수를 만들어서 쓸 수 있도록 해줍니다. 가만 생각해보면 이전 글에서 0b800h:0h 위치에 데이터를 썼는데요 이 메모리 번지를 변수로 만들어 놓으면 간편해지지 않을까요?(주1) 4 | 5 | 일단 간단한 변수를 만들어보겠습니다. 백문이 불여일코라 백번 들어도 한번 코딩해보는게 더 잘 이해되지요. 다음 코드를 실행해보겠습니다. 6 | 7 | ``` 8 | ORG 100h 9 | 10 | MOV AL, var1 11 | MOV BX, var2 12 | 13 | RET ; stops the program. 14 | 15 | VAR1 DB 7 16 | var2 DW 1234h 17 | ``` 18 | 19 | 이전에도 말씀드렸듯이 대소문자 구분은 하지 않습니다. VAR1이나 var1이나 같습니다. db는 define byte 혹은 data byte라고 읽을 수 있습니다. 한바이트의 변수를 선언하는 것입니다. dw는 한 워드의 변수를 선언하는 것입니다. 20 | 21 | 에물레이터를 실행해서 프로그램을 실행해보면 AL과 BX 값이 7과 1234h로 변하는 것을 확인할 수 있습니다. 22 | 23 | 간단하지요? 그런데 여기에 더 중요하게 봐야할 것이 있습니다. 변수는 메모리의 위치라고 말씀드렸습니다. 우리가 만든 var1, var2의 메모리 위치는 어디는 어디일까요? 우리는 메모리의 어디를 변수로 이름지은 걸까요? 24 | 25 | 해답은 에물레이터에 있습니다. 우리가 만든 변수의 값이 7과 1234h입니다. 에물레이터 어딘가에 7과 1234가 보이면 그게 우리가 만든 변수의 위치입니다. 26 | 27 | ![](/assets/2524.png) 28 | 29 | 30 | ret 명령어에 해당하는 숫자는 C3입니다. C3 명령은 메모리 7107h번지에 저장되어 있습니다. ret 명령이 실행되면 프로그램은 끝나게 되는데요 바로 ret 명령 다음에 07과 3412가 보입니다. 31 | 32 | 그리고 에물레이터 화면 오른쪽을 보면 mov al, [00108h] 라고 써진게 보입니다. [] 표시안에는 메모리의 주소가 들어간다고 말씀드렸습니다. 즉 al에 저장할 변수 var1의 주소가 ds:108h라는 것입니다. ds를 깜박하고 주소가 108h라고 생각할 수 있습니다. 항상 세그먼트 레지스터를 생각해야 합니다. ds의 값이 700h 이므로 최종 주소값은 7108h입니다. 33 | 34 | 바로 우리가 선언한 변수입니다. 우리가 변수들을 ret 명령 바로 다음에 선언했는데 그게 실제 프로그램에서도 ret 명령 바로 다음에 저장된 것입니다. 35 | 36 | 이상한건 1234h를 저장했는데 왜 3412로 보인다는 것입니다. 이것은 little endian 이라는 규약입니다. 작은 인디언이 아니라 endian입니다. 반대로 big endian도 있습니다. little endian은 프로그램에서 메모리에 쓴 값을 반대 순서로 저장하는 것입니다. 16비트 1234h를 쓰면 34, 12 순서로 저장되고 32비트 12345678h을 저장하면 78, 56, 34, 12 로 저장됩니다. 메모리의 단위가 바이트이므로 87654321로 저장되는게 아니라 바이트 단위로 나눠져서 순서가 바뀝니다. 여러가지 이유가 있지만 자세한 것은 위키피디어등을 참고하시고 이 글에서는 일단 그렇다는 것만 알고 넘어가겠습니다. (주2) 두꺼운 전공책도 아니고 새로 나오는 개념들을 모두 설명하려고하면 끝이 없습니다. 핵심 줄기에 집중하겠습니다. 37 | 38 | 우리가 소스 파일에 변수를 선언하면 프로그램에도 그 위치대로 변수가 생겨날까요? 이렇게 해보면 알겠지요. 39 | ``` 40 | ORG 100h 41 | 42 | MOV AL, var1 43 | VAR1 DB 7 44 | MOV BX, var2 45 | 46 | RET ; stops the program. 47 | 48 | var2 DW 1234h 49 | ``` 50 | 에물레이터로 돌려보세요. mov al, var1 다음에 7 값이 보이나요? 그리고 al에 저장하려는 메모리의 주소값이 몇인가요? 51 | 52 | ![](/assets/2525.png) 53 | 54 | 다시 한번 말씀드리지만 컴퓨터는 변수 이름이 뭔지 모릅니다. 오직 숫자만 압니다. 103h라는 숫자를 주소값으로 넘겨주면 해당 메모리의 위치를 읽어서 레지스터로 가져오는 것입니다. 55 | 56 | 그럼 변수 값 자체가 주소값이면 어떻게 될까요? 즉 포인터 변수를 흉내내보면 어떨까요? 57 | ``` 58 | ORG 100h 59 | 60 | MOV word ptr [addr], 1234h 61 | mov bx, addr 62 | mov word ptr [bx], 1234h 63 | 64 | RET ; stops the program. 65 | 66 | addr DW 120h 67 | ``` 68 | 소스를 보면 addr 변수가 있는데 120h 입니다. 즉 우리는 ds:[120h] 위치에 1234h 값을 저장하려는 것입니다. 에물레이터를 실행해보세요. mov word ptr [addr], 1234h 명령이 우리가 생각했던대로 mov word ptr [120h], 1234h 로 어셈블링 되었나요? 아니니까 제가 물어본 것이겠지요. 69 | 70 | 어셈블링된 코드를 보면 mov word ptr [10fh], 1234h로 나타납니다. 왜 그럴까요? 제가 바로 위에서 말씀드렸듯이 변수란 메모리 위치이기 때문입니다. addr이 위치한 메모리 주소가 몇인가요? 값 120h를 찾아보세요. little endian 규약에 따라 20, 01 이라고 써있겠지요. 바로 10fh 입니다. 변수는 값이 아니라 메모리 위치라는 것은 몇번을 강조해도 지나치지 않습니다. 71 | 72 | mov al, var1 의 어셈블링 코드는 mov al, byte ptr [var1] 이었습니다. 변수를 메모리 위치로 생각하는 것입니다. mov al, var1이 마치 var1 이라는 문자를 변수 값 7로 바꾸는 것처럼 보이지만 사실은 var1의 메모리 주소를 계산하고 그 다음 메모리에서 해당 주소값에 있는 데이터를 읽어오는 것입니다. 73 | 74 | mov al, var1 과 mov al, byte ptr [var1] 이 같은 명령이라는게 이상하게 느껴질 수도 있습니다. 하지만 mov al, var1은 어셈블러가 인간이 보기 편하도록 제공하는 기능이라고 생각하고 원래 기계가 이해하는 문법은 mov al, byte ptr [var1]이라고 생각하셔야 합니다. 75 | 76 | 그럼 변수에 주소 값일 저장하는 것은 어떻게 해야할까요? 소스 코드에도 써놓았듯이 bx레지스터에 주소 값을 저장하고 [bx] 명령으로 접근하면 됩니다. 77 | 78 | mov word ptr [addr], 1234h 명령이 실행되면 addr 변수에 1234h가 저장됩니다. 그리고 mov bx, addr 명령을 실행하면 bx 레지스터에 1234h 값이 저장됩니다. 그리고 마지막으로 ds:[bx] 위치에 1234h 값을 저장합니다. 79 | 80 | 에물레이터의 aux 버튼을 눌러서 메모리 창을 열고 0700:1234 주소의 메모리를 확인해보세요. 34 12 라는 값이 나타날 것입니다. 81 | 82 | 잠시 C 언어에 대한 이야기를 하겠습니다. C 언어에 관심없으신 분은 넘기셔도 좋습니다. 83 | 84 | C 언어에서 포인터 변수를 읽어서 특정한 메모리 위치에 값을 쓰는 것을 간접 참조라고 부릅니다. 왜 간접일까요? 우리가 어셈블리 언어로 직접 만들었듯이 포인터 변수에 저장된 주소값에 접근하러면 우선 변수에 저장된 주소 값을 레지스터로 읽어와야 합니다. 그리고 레지스터 값을 이용해서 메모리에 접근할 수 있지요. 85 | 86 | 일반 변수는 한번에 접근됩니다. 변수의 주소를 알기때문이지요. 포인터 변수는 변수를 한번 레지스터로 읽어오고 그 다음에야 최종 접근하려는 위치에 접근됩니다. 이렇게 한번 더 메모리를 읽는 절차가 있기 때문에 간접 접근이라고 부르는 것입니다. 87 | 88 | 몇번째 말씀드리는건지 모르겠네요. C는 어셈블리의 확장판입니다!! 89 | 90 | 91 | 92 | 93 | 94 | 주1: unsigned short *p = 0xb8000; 이렇게 만들면 일일이 주소값을 쓰는 것보다 변수로 주소를 지정할 수 있겠지요. 95 | 96 | 주2: little endian이라는 것을 모른다는 사실을 알았네요? 한걸음 걸었으니 little endian에 대해 알아봅시다. 그러면 모른다는 것도 모르는 상태에서 모른다는 것을 아는 상태가 되고, 또 뭔가를 아는 상태가 되는 것입니다. 97 | 98 | -------------------------------------------------------------------------------- /biosinterrupt.md: -------------------------------------------------------------------------------- 1 | #바이오스가 제공하는 인터럽트 2 | 3 | 호랑이 담배피던 시절 이야기를 하겠습니다. 그냥 재미로 봐주세요. 요즘 환경과는 별로 관련이 없습니다. 4 | 5 | 옛날 옛적에 바이오스라는게 있었습니다. 지금도 있지만 옛날에는 조금 달랐습니다. 지금은 윈도우를 부팅시켜주는 역할이 대부분이지요. 사실은 그보다 더 복잡하지만 99%의 사용자와 99%의 개발자들은 그 이상 알 필요가 없습니다. 그런데 옛날 옛적 도스 시절에는 바이오스란 개발에 없어서는 안될 중요한 자원이었습니다. 바이오스가 제공하는 함수들이 바로 인터럽트입니다. 6 | 7 | 원래 인터럽트는 그게 아닌데? 생각하시는 분들이 많으실겁니다. 맞습니다. 원래 인터럽트란 외부 장치로부터 들어오는 신호가 인터럽트이지요. 정확히 말하면 바이오스가 제공하는 예외처리나 소프트웨어 인터럽트라고 하는게 맞습니다. 하지만 프로세서 명령어의 이름이 인터럽트라서 인터럽트라고 보통 부릅니다. 8 | 9 | 중요한건 이름이 뭐냐가 아니라 무슨 일을 하느냐겠지요. 바이오스가 제공하는 기능이 뭐냐가 중요한건데 주로 장치들을 제어하는 기능을 제공합니다. 모니터에 글자를 출력하는 인터럽트가 있습니다. 이전에 함수 만들기 예제에서 int라는 명령어가 있었습니다. 그게 인터럽트 명령어입니다. 모니터 (정확히는 비디오 출력)을 제어하는 것입니다. 10 | 11 | 또 옛날 컴퓨터에 어떤 장치들이 있었는지 생각해보세요. 모르시는 분들이 많으시겠지만 플로피 디스크라는게 있었고, 또 그 옛날에도 부자들은 하드디스크를 썼었습니다. 전화선을 꼽아서 쓰는 모뎀이라는게 있어서 하이텔에 접속해서 동호회활동도 했었지요. 테이프를 써서 음악을 듣는게 아니라 게임을 하기도 했습니다. 12 | 13 | 이런 장치들을 프로그래밍하는데 바이오스의 인터럽트를 사용하는 것입니다. 왜 그랬냐면 컴퓨터라는게 IBM-compatible이라는 일정한 규칙대로 만들어지기 때문입니다. 자세한건 저도 모릅니다. 스펙 문서는 따분하고 어렵습니다. 그냥 컴퓨터를 만들기 위한 규칙이 있는데 그런 규칙에 따라 이런 장치는 이렇게 조정해야한다라는 세부 규칙이 있습니다. 그런 규칙을 프로그래머가 일일이 프로그래밍해야되면 너무 복잡하니까 바이오스 회사에서 라이브러리처럼 제공해주는 것입니다. 14 | 15 | 요즘은 운영체제가 그런 일을 합니다. 리눅스 커널을 보면 모든 장치들을 제어하기 위한 프로그램들이 들어있어서 바이오스는 리눅스 커널이 부팅된 이후부터는 아무런 일도 안해도 됩니다. 저는 컴퓨터가 부팅된 다음에 바이오스에 연결된 동그란 건전지를 빼서 바이오스를 정지시켜봤습니다만 컴퓨터는 계속 잘 동작했습니다. 16 | 17 | 옛날옛날에는 그런 운영체제가 없었습니다. 도스가 있었다고요? 도스는 운영체제가 아닙니다. 그냥 쉘이라고 부르는게 더 정확합니다. 높은 버전의 도스는 스케줄링도 한다고 들은것 같은데 원래의 도스는 그냥 사용자가 입력한 프로그램을 실행시켜주는 실행시나 쉘 정도였습니다. 어쨌든 그래서 바이오스의 인터럽트를 많이 사용했었습니다. 18 | 19 | 역사 이야기는 그만하고 다시 인터럽트로 돌아갑시다. 예를 들어 문자를 화면에 출력하기 위해서는 화면의 몇번째 줄 몇번째 칸에 글자를 출력해야할지 프로그램에서 관리를 해야했습니다. 하지만 인터럽트를 이용하면 그런 위치에 신경쓸 필요없이 문자를 출력할 수 있고, 한 화면을 다쓰면 알아서 스크롤도 해줍니다. 스크롤하는 코드도 은근히 긴데 이렇게 라이브러리처럼 만들어줬으니까 편리합니다. 20 | 21 | 인터럽트를 실행하는 명령어는 int 입니다. 그리고 인터럽트의 번호를 쓰면 됩니다. 22 | 23 | ``` 24 | int 10h 25 | ``` 26 | 27 | 인터럽트 번호는 0부터 255까지입니다. 그렇다고 인터럽트가 256개인것은 아닙니다. 인터럽트 번호와는 따로 sub 번호라는게 있어서 인터럽트를 호출할 때 ah 레지스터에 sub 번호를 지정합니다. 그래서 총 256*256 개의 인터럽트가 존재합니다. 28 | 29 | 다음 예제는 화면에 Hello를 출력하는 예제입니다. 30 | ``` 31 | ORG 100h ; directive to make a simple .com file. 32 | 33 | ; The sub-function that we are using 34 | ; does not modify the AH register on 35 | ; return, so we may set it only once. 36 | 37 | MOV AH, 0Eh ; select sub-function. 38 | 39 | ; INT 10h / 0Eh sub-function 40 | ; receives an ASCII code of the 41 | ; character that will be printed 42 | ; in AL register. 43 | 44 | MOV AL, 'H' ; ASCII code: 72 45 | INT 10h ; print it! 46 | 47 | MOV AL, 'e' ; ASCII code: 101 48 | INT 10h ; print it! 49 | 50 | MOV AL, 'l' ; ASCII code: 108 51 | INT 10h ; print it! 52 | 53 | MOV AL, 'l' ; ASCII code: 108 54 | INT 10h ; print it! 55 | 56 | MOV AL, 'o' ; ASCII code: 111 57 | INT 10h ; print it! 58 | 59 | MOV AL, '!' ; ASCII code: 33 60 | INT 10h ; print it! 61 | 62 | RET ; returns to operating system. 63 | ``` 64 | 인터럽트 번호는 10h이고 서브번호는 0eh입니다. 인터럽트마다 필요한 데이터를 전달하는 방식이 다른데 int 10h는 al에 출력할 글자를 저장해서 전달합니다. 65 | 66 | http://gurugio.kldp.net/wiki/wiki.php/emu8086/%EC%9D%B8%ED%84%B0%EB%9F%BD%ED%8A%B8%EB%A6%AC%EC%8A%A4%ED%8A%B8 67 | 68 | 그 외에 어떤 인터럽트들이 있는지는 제 홈페이지 링크를 참고하시기 바랍니다. 별별 기능들이 다 있습니다. 69 | 70 | ##인터럽트의 호출 71 | 72 | 인터럽트를 호출하는 실행되는 프로그램들은 어디 있는걸까요? 바이오스 칩 안에 있는 메모리에 있습니다. 컴퓨터 슬롯에 끼워쓰는 메모리에 있는게 아닙니다. 단지 메모리에 있는 것처럼 메모리 주소를 가지고는 있습니다. 73 | 74 | 8086 환경에서는 인터럽트 프로그램의 시작 주소가 0f4000h입니다. 75 | 76 | 예를 들면 int 12h는 메모리가 몇 kb인지를 알려주는 인터럽트입니다. 호출하면 ax에 kb 단위로 얼마인지가 저장됩니다. 그리고 이 프로그램은 0f400h:01adh 주소에 있습니다. int 12h를 호출하는대신 call 0f400h:01adh라고 호출해도 환경에따라 될 수도 있습니다. 에물레이터에서는 될지 안될지 모르겠습니다. 한번 해보세요. 실제 컴퓨터에서는 됩니다. 77 | 78 | 중요한건 어디있냐가 아닙니다. 그냥 int 명령이 어디있는지 찾아서 호출합니다. 우리가 생각해볼건 0f400h:01adh라는 주소에 있는 함수를 호출했다가 복귀하기 위해서는 일반적인 함수 호출과는 다른게 필요하다라는 것입니다. 79 | 80 | 일반적인 함수 호출은 call 1234h이고 스택에 복귀 주소 10bh가 저장됩니다. 그런데 여기에 생략된게 있습니다. 실행되는 코드의 주소는 원래 cs:ip로 표시됩니다. 데이터가 ds:bx로 표시되는 것과 마찬가지입니다. 모든 주소는 세그먼트 레지스터와 일반 레지스터로 표시하게 되있습니다. 81 | 82 | 그럼 인터럽트 함수의 주소는 f400h:1adh이고 복귀해야할 주소는 700h:10b입니다. 세그먼트 주소가 다릅니다. 인터럽트가 실행될때 cs는 f400h이고 ip는 1adh일텐데 스택에 복귀주소가 10b가 저장되어있으면 f400h:10bh로 복귀해서 제대로 복귀되지 않고 또다시 어떤 인터럽트의 코드가 실행됩니다. 83 | 84 | 따라서 인터럽트를 호출할 때는 세그먼트의 주소도 스택에 저장해서 복귀할 때 세그먼트 주소와 ip 주소가 모두 되돌려져야합니다. 이런걸 far call 이라고 합니다. 단순히 16비트의 ip 주소만 바꾸는게 아니라 더~~ 멀리 가도록 한다는 것입니다. 85 | 86 | int 명령을 실행한 후에 스택에 저장된 값들을 확인해보세요. 87 | 88 | 다시한번 말씀드리지만 인터럽트의 내용들은 운영체제를 만들거나하지 않는 이상 별로 필요없는 내용입니다. 그냥 컴퓨터의 내부 원리를 알고싶으신 분들은 재미로 보셔도 좋습니다. 어쨌든 far call이라는게 있다는건 좀 흥미롭긴 합니다. -------------------------------------------------------------------------------- /function.md: -------------------------------------------------------------------------------- 1 | 2 | #함수 만들기 3 | 4 | 함수를 어떻게 만드는지 설명하겠습니다. 어셈블리 언어에서는 함수를 프로시저라고 부릅니다. 파스칼같은 옛날 언어들은 함수를 프로시저라고 부르던데 사실 무슨 차이인지는 모르겠습니다. 어셈블리에서는 proc 이라는 키워드를 사용하므로 프로시저라고 말하는게 더 맞는 것이긴 하지만 요즘 시대에 맞지 않으므로 저는 함수라고 부르겠습니다. 5 | 6 | ##함수 만들기 7 | 8 | 함수는 이렇게 만듭니다. 9 | 10 | 11 | ``` 12 | 함수이름 PROC 13 | 14 | ; 함수 내용 15 | 16 | ret 17 | 18 | 함수이름 ENDP 19 | ``` 20 | 21 | 22 | PROC과 ENDP는 키워드입니다. 실제로 실행되는 명령어가 아니라 어셈블러에게 여기부터 여기까지가 함수의 내용이라고 알려주는 역할을 합니다. 23 | 24 | 함수이름은 PROC와 ENDP에서 두번 나타납니다. 이름을 다르게쓰면 안됩니다. 이름에 대한 규칙이 있습니다만 어셈블러마다 다르므로 굳이 복잡하게생각하지 않고 영문과 숫자와 _ 기호 정도만 사용하도록 하겠습니다. 25 | 26 | ret 명령이 나왔는데 지금까지 역할이 뭔지 모르고 있었지만 지금에서 말씀드리면 함수를 호출한 위치로 되돌아가는 명령입니다. 어떻게 되돌아가는지 세부적인 내용은 스택의 개념을 알아야 하므로 또 다시 완벽한 설명을 뒤로 미룰 수밖에 없습니다. 지금은 함수의 마지막에는 항상 ret을 써야한다는 걸로 넘어가겠습니다. 27 | 28 | 함수를 호출하는 명령은 call 입니다. 예제를 한번 보시겠습니다. 29 | 30 | 31 | ``` 32 | ORG 100h 33 | 34 | CALL m1 35 | 36 | MOV AX, 2 37 | 38 | RET ; return to operating system. 39 | 40 | m1 PROC 41 | MOV BX, 5 42 | RET ; return to caller. 43 | m1 ENDP 44 | 45 | END 46 | ``` 47 | 48 | m1이라는 함수를 만듭니다. call m1 명령이 함수를 호출하는 명령입니다. m1함수는 bx 에 5를 저장하고 되돌아갑니다. 함수가 끝나면 call 명령의 다음으로 이동하므로 mov ax, 2 명령이 실행됩니다. 그러면 ax에 2가 써지고 ret이 실행됩니다. 49 | 주석에는 운영체제로 복귀한다고 써있는데 무슨 뜻이냐면 운영체제가 이 프로그램을 함수 호출처럼 호출했다는 뜻입니다. 운영체제에서 call 명령으로 프로그램을 실행한다는 것입니다. 우리 프로그램의 이름을 어떻게할고 함수처럼 호출했을까요? 또 또 이름이 주소라는 설명을 해야됩니다. 이제 좀 지겹네요. m1이라는 함수 이름도 mov bx, 5 라는 명령이 있는 메모리 주소로 변환됩니다. 우리 프로그램에서는 아마 107h일 것입니다. 그럼 운영체제가 우리 프로그램의 이름을 알 필요가 없다는걸 알 수 있습니다. 대신 call 700h:100h 명령을 실행하면 우리 프로그램이 실행됩니다. 심심하신 분은 예제 프로그램에서 call m1을 call 700h:100h로 바꿔보세요. 무한히 프로그램이 실행되는 것을 볼 수 있습니다. 700h는 cs의 값이고 100h의 ip의 값을 지정하는 것입니다. cs, ip 레지스터의 값은 직접 바꿀 수 없다고 설명했었습니다. 대신에 call 명령으로 바꿀 수 있고 바로 프로세서가 실행해야할 위치를 지정하는 것입니다. 실행 위치또한 메모리의 주소입니다. 50 | 51 | 마지막은 ret 명령으로 운영체제로 복귀하는 것입니다. 운영체제가 하던 일을 계속하는 것입니다. 요즘 운영체제라면 다른 프로그램을 실행할 것이고 도스같은 운영체제라면 아무것도 안하고 사용자가 다른 프로그램을 실행시키길 기다릴 것입니다. 그건 운영체제가 알아서 할 일이니까 우리가 할 일은 우리 프로그램이 끝났다는 표시만 하면 되는 것입니다. 52 | 53 | ##레지스터로 함수 인자 전달하기 54 | 55 | 함수에서 빠지면 안되는게 함수 인자입니다. 인자가 없다면 함수가 별로 필요없겠지요. 56 | 57 | 그런데 함수 인자를 전달하는 방법은 매우 다양합니다. 레지스터에 저장해서 전달하는 방법이 가장 간단합니다. 그런데 인자가 5개면 어떻게 해야할까요? si나 di 레지스터를 써야할까요? 또 함수 호출 전에 각 레지스터에 있던 값들은 어떻게 해야할까요? 인자가 4개라면 ax, bx, cx, dx 레지스터를 모두 써야되는데 기존에 있던 값들이 지워지지 않게 해야되는데 어떻게 해야할까요? 이런 사항들을 고려해서 함수 호출에 대한 규칙을 정한게 Calling convention 호출 규약이라는 것입니다. 언어마다 운영체제마다 다른 호출 규약을 씁니다. 58 | 59 | 지금은 간단하게 레지스터에 인자를 저장하고 함수를 호출하겠습니다. 다음 예제는 al, bl 레지스터에 값을 저장하고 함수를 호출하면 함수는 곱셈을 하고 결과 값을 ax 레지스터에 저장해주는 예제입니다. 60 | 61 | 62 | ``` 63 | ORG 100h 64 | 65 | MOV AL, 1 66 | MOV BL, 2 67 | 68 | CALL m2 69 | CALL m2 70 | CALL m2 71 | CALL m2 72 | 73 | RET ; return to operating system. 74 | 75 | m2 PROC 76 | MUL BL ; AX = AL * BL. 77 | RET ; return to caller. 78 | m2 ENDP 79 | 80 | END 81 | ``` 82 | 83 | bl 레지스터의 값은 바뀌지 않겠지요. al 레지스터의 값은 2의 제곱, 세제곱, 네제곱이 되서 ax 레지스터에는 2^4의 값이 저장됩니다. 84 | 85 | ##쬐끔 고급 예제 86 | 87 | 88 | 89 | 그냥 눈으로만 보셔도 좋은 예제입니다. 화면에 Hello World! 라는 메세지를 출력하는 프로그램입니다. lea, call, cmp, byte ptr, jmp 명령어를 쓰고 함수도 만들고 함수 인자로 si를 사용하고 있는 종합선물세트입니다. int 10h 명령어만 대강 화면에 al 레지스터에 있는 문자를 출력한다고만 생각하시고 읽어보시기 바랍니다. 90 | 91 | 92 | ``` 93 | ORG 100h 94 | 95 | LEA SI, msg ; load address of msg to SI. 96 | 97 | CALL print_me 98 | 99 | RET ; return to operating 100 | 101 | system. 102 | 103 | ;========================================================== 104 | ; this procedure prints a string, the string should be null 105 | ; terminated (have zero in the end), 106 | ; the string address should be in SI register: 107 | print_me PROC 108 | 109 | next_char: 110 | CMP byte ptr [SI], 0 ; check for zero to stop 111 | JE stop ; 112 | 113 | MOV AL, [SI] ; next get ASCII char. 114 | 115 | MOV AH, 0Eh ; teletype function number. 116 | INT 10h ; using interrupt to print a char in AL. 117 | 118 | ADD SI, 1 ; advance index of string array. 119 | 120 | JMP next_char ; go back, and type another char. 121 | 122 | stop: 123 | RET ; return to caller. 124 | print_me ENDP 125 | ; ========================================================== 126 | 127 | msg DB 'Hello World!', 0 ; null terminated string. 128 | 129 | END 130 | ``` -------------------------------------------------------------------------------- /conditionaljump.md: -------------------------------------------------------------------------------- 1 | # 필요할 때만 점프하기 2 | 3 | jmp 명령이 무조건으로 점프를 하는데 어떤 상태가 되었을 때만 점프를 하는 명령어도 있습니다. 3종류가 있는데 하나는 프로세서의 상태 비트를 확인해서 점프를 하는 것이고 두번째는 부호가 있는 숫자를 비교해서 점프를 하는 것이고 마지막은 부호없는 숫자의 비교 결과로 점프를 하는 것입니다. 4 | 5 | ## 플래그 상태를 보고 점프하기 6 | 7 | 다음 표는 프로세서 상태 플래그에 따라서 점프를 하는 명령어들의 표입니다. 그런데 어떤 명령어들의 설명을 보면 0일때 혹은 같을때라는 설명이 있습니다. 같은 것과 0인게 무슨 관계이길래 이거나 저거나 같다는 설명을 할까요? 8 | 9 | 몇번째인지도 모르겠지만 또 프로세서는 모든걸 숫자로만 본다는 이야기를 하게됩니다. 숫자가 같다와 0이다를 보면 딱 생각나는게 있지 않나요? 바로 빼기입니다. 숫자의 비교라는 것은 결국 두 숫자를 뺀다는 것입니다. 예를 들어 루프를 10번 하려고할때 cx에 0을 넣고 cx를 하나씩 증가시킵니다. 그리고 cx값이 10인지 확인하는대 빼기 연산을 한다는 것입니다. 빼기를 하다보면 zf 플래그가 1이 되는 순간이 있겠지요 그러면 cx 값이 10이라는걸 알 수 있게 됩니다. 10 | 11 | 물론 sub cx, 10 명령으로 빼기를 하면 cx 값이 바뀌겠지요 그래서 빼기는 하되 값은 안바꾸는 cmp 명령도 있습니다. 어쨌든 sub, xor 같이 레지스터 값을 0으로 만드는 연산을 하게되면 zf 플래그가 1이 됩니다. 그래서 루프를 다 돌았는지, 원하는 결과값이 나왔는지 안나왔는지 등을 확인할 수 있게 됩니다. 그리고 그런 상황에 따라 점프를 하게 되는 것이지요. 루프를 다 안돌았으면 다시 루프의 시작 명령으로 점프를 하면 되고, 원하는 값이 안나왔으면 에러 처리로 점프하면 되는 것입니다. 12 | 13 | | 명령어 | 설명 | 조건 | 반대명령어 | 14 | | :--- | :--- | :--- | :--- | 15 | |jz, je| 0일때(같을때) 점프 |zf=1| jnz,jne 16 | |jc, jb, jnae| 각각 캐리가 생겼을때, 작을때, 크거나 같지 않을때 점프 cf=1 | jnc, jnb, jae | 17 | |js | 계산 결과 부호 비트가 켜졌을 때 점프 | sf=1 | jns | 18 | |jo | 오버플로우가 발생했을 때 점프 | of=1 | jno | 19 | |jpe, jp| Parity Even 상태가 되었을 때 점프 | pf=1 | jpo | 20 | |jnz, jne| 0이 아니거나 같지 않을 때 점프 | zf=0 | jz, je | 21 | |jnc, jnb, jae| 캐리가 없을때, 작지 않을때, 크거나 같을 때 점프 | cf=0 | jc, jb, jnae | 22 | |jns| 부호 비트가 0일 때 점프 | sf=0 | js | 23 | |jno| 오버플로우가 아닐 때 점프 | of=0 | jo | 24 | |jpo, jnp| Parity Odd 상태일 때 점프 | pf=0 | jpe, jp | 25 | 26 | 27 | 크거나 같지 않다는 것은 작다는 것이고 작지 않다는 것은 크거나 같다는 것입니다. 필요에 따라 어떤 상황을 쓸것인지 결정하면 됩니다. 28 | 29 | 또 jz, je 같이 이름은 다르지만 같은 일을 하는 명령어가 있습니다. 이런 명령어들은 기계어도 같습니다. 그래서 내가 코드에 jz를 써놨어도 에뮬레이터를 보면 je로 표시될 수도 있고 그 반대일 수도 있습니다. jc와 jb도 같은 명령이지요. 이렇게 같은 일을 하지만 다른 명령어들을 만들면 프로그래머가 코드를 이해하기 쉬워집니다. 하지만 에뮬레이터나 디스어셈블러 같은 툴들은 같은 기계 코드를 만나므로 같은 명령어중에 어떤걸 의도했는지 모릅니다. 30 | 31 | 다음 코드를 에물레이터로 실행해보면 모두 jnb 명령으로 어셈블되는 것을 볼 수 있습니다. jnb 명령의 기계 코드는 2바이트로 제한되어있는데 jnb 명령 자체가 73h로 1바이트입니다. 따라서 주소를 표현할 수 있는 자리가 1바이트만 남으므로 점프할 수 있는 범위가 1바이트가 됩니다. 1바이트라면 부호를 고려했을 때 -128~127이므로 명령어가 있는 위치로부터 위로 128바이트까지 되돌아 올라갈 수 있고 127바이트까지 아래로 내려갈 수 있게 됩니다. 32 | 33 | 즉 이번에 설명된 점프 명령들은 16비트 점프가 아닌 8비트 점프들입니다. 34 | ``` 35 | jnc a 36 | jnb a 37 | jae a 38 | 39 | mov ax, 4 40 | a: mov ax, 5 41 | ret 42 | ``` 43 | 만약 프로그램이 너무 길어져서 128바이트 범위를 벗어난 지점으로 점프하고 싶으면 어떻게 해야될까요? 이 예제를 실행해서 에물레이터에 어떻게 어셈블되었는지 확인해보면 알 수 있습니다. 현대 프로그래밍 환경에서는 전혀 필요없는 내용이므로 따로 설명하지는 않겠습니다. 궁금하면 한번 돌려보세요. 참고로 ARM같은 최신 프로세서도 RISC라는 특성때문에 점프할 수 있는 범위가 작습니다. 그래서 다양한 해결 방법들이 있습니다만 컴파일러가 알아서 해주는 것이지요. 운영체제나 드라이버를 개발해야할때만 그런 특성을 고려하게됩니다. 44 | ``` 45 | jz a 46 | jb a 47 | jnc a 48 | c DB 128 DUP\(9\) 49 | a: 50 | ret 51 | ``` 52 | 53 | ##부호있는 숫자를 비교해서 점프하기 54 | 55 | 1바이트의 숫자를 비교할 때 255와 10중에 어느게 더 큰가는 부호를 따지느냐 아니냐에 따라 다릅니다. 부호를 고려한다면 255는 -1이고 10은 10이므로 10이 더 큰 것이고 부호를 뺀다면 255가 더 크게 됩니다. 따라서 점프 명령도 빼기를 하므로 부호가 있냐 없냐가 중요하게 됩니다. 56 | 57 | 부호있는 숫자들을 비교해서 점프하는 명령어는 다음 테이블에 있습니다. 58 | 59 | |명령어 | 설명 | 조건 | 반대 명령어 | 60 | | :--- | :--- | :--- | :--- | 61 | |JE , JZ | Jump if Equal \(=\). Jump if Zero. | ZF = 1 | JNE, JNZ | 62 | |JNE , JNZ | Jump if Not Equal \(<>\). Jump if Not Zero. | ZF = 0 | JE, JZ | 63 | |JG , JNLE | Jump if Greater \(>\). Jump if Not Less or Equal \(not <=\). | ZF = 0 and SF = OF | JNG, JLE | 64 | |JL , JNGE | Jump if Less \(<\). Jump if Not Greater or Equal \(not >=\). | SF <> OF | JNL, JGE | 65 | |JGE , JNL | Jump if Greater or Equal \(>=\). Jump if Not Less \(not <\). | SF = OF | JNGE, JL | 66 | |JLE , JNG | Jump if Less or Equal \(<=\). Jump if Not Greater \(not >\). | ZF = 1 or SF <> OF | JNLE, JG| 67 | 68 | <>는 같지 않다는 표시입니다. 별로 설명하게 없습니다. 크냐 작냐 뿐이니까요. 참고로 크지 않다는 작거나 같다는 것이고 작지 않다는 크거나 같다는 것입니다. > 의 반대는 <= 이라는거 당연하지만 가끔 =을 빼먹으면 골치아픈 버그가 생깁니다. 69 | 70 | ##부호없는 숫자를 비교해서 점프하기 71 | 72 | |명령어 | 설명 | 조건 | 반대 명령어 | 73 | | :--- | :--- | :--- | :--- | 74 | |JE , JZ |Jump if Equal \(=\). Jump if Zero. | ZF = 1 | JNE, JNZ | 75 | |JNE , JNZ | Jump if Not Equal \(<>\). Jump if Not Zero. | ZF = 0 | JE, JZ | 76 | |JA , JNBE | Jump if Above \(>\). Jump if Not Below or Equal \(not <=\). | CF = 0 and ZF = 0 | JNA, JBE | 77 | |JB , JNAE, JC | Jump if Below \(<\). Jump if Not Above or Equal \(not >=\). Jump if Carry. | CF = 1 | JNB, JAE, JNC | 78 | |JAE , JNB, JNC | Jump if Above or Equal \(>=\). Jump if Not Below \(not <\). Jump if Not Carry. | CF = 0 | JNAE, JB | 79 | |JBE , JNA | Jump if Below or Equal \(<=\). Jump if Not Above \(not >\). | CF = 1 or ZF = 1 | JNBE, JA| 80 | 81 | 이정도로 점프 명령어는 마무리하겠습니다. 82 | 83 | -------------------------------------------------------------------------------- /number.md: -------------------------------------------------------------------------------- 1 | # 컴퓨터 세상의 원리: 수 2 | 3 | 숫자가 먼저입니다. 4 | 5 | 어셈블리보다 숫자가 먼저 태어났지요. 수는 모든 만물의 원리라고 피타고라스도 말했습니다. 당연히 컴퓨터도 숫자로 돌아갑니다. 수학자들이 컴퓨터라는 시스템을 생각해낸게 이상하지 않지요. 6 | 7 | 왜 컴퓨터가 숫자로 이루어졌는지는 점점 자연스럽게 알게됩니다. 지금은 일단 어셈블리 언어에 사용하는 2진수와 16진수를 알아보겠습니다. 2진수 16진수를 알아야 어셈블리 언어를 입력하고 이해할 수 있습니다. 일단 emu8086 화면에 나오는 모든 숫자가 16진수이기 때문에라도 16진수가 뭔지 알아야합니다. 8 | 9 | ## 10진수 10 | 11 | 우리는 10진수를 씁니다. 0부터 9까지의 숫자 표기를 이용해서 수를 표현합니다. 이 숫자를 여러개 붙여서 특정한 값을 표현합니다. 예를 들어 754는 각 자리마다 10의 지수승으로 이루어져있습니다. 12 | 13 | 7 * 10^2 + 5 * 10^1 + 4 * 10^0 14 | 15 | 7은 10의 제곱승, 5는 10의 1승, 4는 10의 0승입니다. 10의 0승은 1이고요. 16 | 17 | 만약에 자리가 바뀌면 완전히 다른 숫자가 됩니다. 574는 754가 아니지요. 18 | 19 | ## 2진수 20 | 21 | 컴퓨터는 전자적으로 2가지 상태를 가진 물질로 이루어져있습니다. 전자적인 신호가 있으면 1 없으면 0의 상태를 가진 물질입니다.(주1) 그래서 0부터 9까지 10개의 숫자를 기억할 수 없습니다. 그래서 컴퓨터는 2진수를 사용하는 것입니다. 22 | 23 | 2진수란 수를 나타내는 숫자 표기가 2개인 것입니다. 0과 1입니다. 10진수는 열개의 숫자 표기가 있었지요. 24 | 25 | 2진수와 10진수를 구분하기 위해서 우리는 보통 2진수의 숫자 뒤에 b라고 씁니다. 영어로 2진수가 binary이기 때문입니다. 26 | 27 | 2진수의 수 10100101b는 10진수로 몇일까요? 28 | 29 | 10진수 754는 각 자리마다 10의 제곱승을 했습니다. 마찬가지로 2진수도 각 자리마다 2의 제곱승을 하면 됩니다. 가장 오른쪽은 2의 0승, 한칸 왼쪽은 2의 1승 이렇게 자리가 올라갈때마다 2를 곱하면 됩니다. 30 | 31 | 10100101b = 1*2^0 + 1*2^2 + 1*2^5 + 1*2^7 = 1 + 4 + 32 + 128 = 5 + 160 = 165 32 | 33 | 반대로 165를 2진수로 표현하려면 어떻게 해야할까요? 중고등학교 수학시간에 배웠듯이 2로 나눠서 나머지를 나열하는 방법도 있습니다. 그보다 저는 165와 가장 가까운 2의 제곱수를 생각하는 방법을 좋아합니다. 34 | 35 | 가장 먼저 165 = 128 + 37 라고 생각합니다. 128은 2^7인 것을 외우고 있기 때문입니다. 어셈블리 언어를 공부하다보니 2의 제곱수를 자연스럽게 많이 외우게 되었습니다. 36 | 37 | 165 = 2^7 + 37 이라면 그 다음으로 37 = 32 + 5 입니다. 32는 2^5 입니다. 그렇게 5도 4+1 이므로 2^2 + 1입니다. 결론은 165 = 2^7 + 2^5 + 2^2 + 2^0 입니다. 이걸 2진수로 바꾸는 방법은 제곱승의 숫자만큼 0을 붙이는 것입니다. 2^7은 0이 7개이므로 10000000b 입니다. 2^5는 0이 다섯개이므로 100000, 2^2는 100입니다. 다 더하면 10100101b 입니다. 38 | 39 | 어떤 방법으로 계사하던지 아니면 계산기를 쓰던지 상관없습니다. 계산기가 제일 좋겠네요. 40 | 41 | 참고로 8086 어셈블리 언어에서는 2진수의 각 자리를 비트bit라고 부릅니다. 4개의 비트는 니블nibble 8개는 바이트byte 2개의 바이트를 워드word 4개의 바이트를 double word라고 부릅니다. 그리고 가장 오른쪽 비트를 low bit 라고 부르거나 Least Significant Bit라고 부르고 가장 왼쪽 비트는 Most Significant Bit라고 부릅니다. 42 | 43 | 44 | 45 | ## 16진수 46 | 47 | 컴퓨터 업계에서는 16진수를 흔하게 씁니다. 왜 그럴까요? 2진수를 쉽게 변환할 수 있고 또 2진수를 줄여서 쓸 수 있기 때문입니다. 32비트의 주소를 표현할 때 혹은 1024같이 비교적 크기 않은 값을 표현할 때 2진수로 표현하면 32비트 주소는 무려 32자리의 2진수를 써야합니다. 1024는 2^10이므로 0을 10개 써서 10000000000b로 써야합니다. 굉장히 불편합니다. 또 0의 갯수를 잘못 쓸 위험도 많아서 코드를 입력할 때 실수하기가 너무 쉽습니다. 48 | 49 | 그렇다고 10진수로 쓰려면 2진수의 각 자리가 몇번째 자리인지 계산해야합니다. 곱셈도 해야되고 덧셈도 해야되고 구구단을 해본지 한참된 머리나쁜 어른들에게는 너무 어렵습니다. 그래서 16진수를 고른 것입니다. 16진수를 2진수를 4개씩 끊어서 생각하면 되기때문에 비교적 간단합니다. 일이삼사는 쉽자나요? 50 | 51 | 10100101b를 4개씩 끊으면 1010 과 0101입니다. 1010은 10이고 0101은 5입니다.16진수에서 10이라는 숫자가 있을까요? 아닙니다. 16진수에서 10은 A입니다. 16진수를 16개의 숫자가 있겠지요? 그런데 숫자는 10개뿐이므로 알파벳 a,b,c,d,e,f를 빌려와서 16개의 숫자를 표현합니다. 52 | 53 | 10진수로 10은 a, 11은 b, 15는 f입니다. 대소문자는 상관없습니다. 54 | 55 | 그래서 10100101b는 4개로 나눠서 A5가 됩니다. 56 | 57 | 2진수의 숫자가 아무리 길어도 우리는 4개씩 나눠서만 생각하면 됩니다. 길고긴 32비트의 2진수를 4개씩 쪼개면 8자리 16진수로 나타낼 수 있습니다. 다시 말하면 하나의 니블이 16진수 한자리가 되는 것입니다. 58 | 59 | 그리고 2진수를 b로 표시하는 것 같이 16진수로 h로 표시합니다. hexa-decimal이라는 표시입니다. 첫 자리가 알파벳이면 0을 붙이기도합니다. 그래서 A5는 0A5h라고 표시합니다. 또 C 언어에서는 0xA5라고 표시합니다. 0x가 16진수라는 표시입니다. 60 | 61 | 다음은 10진수, 2진수, 16진수를 표로 보여줍니다. 62 | 63 | ``` 64 | 10진수 2진수 16진수 65 | 0 0000 0 66 | 1 0001 1 67 | 2 0010 2 68 | 3 0011 3 69 | 4 0100 4 70 | 5 0101 5 71 | 6 0110 6 72 | 7 0111 7 73 | 8 1000 8 74 | 9 1001 9 75 | 10 1010 A 76 | 11 1011 B 77 | 12 1100 C 78 | 13 1101 D 79 | 14 1110 E 80 | 15 1111 F 81 | ``` 82 | 83 | 84 | 85 | 그럼 16진수의 값 1234h를 10진수로 바꿔보겠습니다. 16의 제곱수가 0에서 시작해서 하나씩 늘어난다고 생각하면 됩니다. 86 | 87 | 1234h = 4*16^0 + 3*16^1 + 2*16^2 + 1*16^3 = 4660 88 | 89 | 4660을 16진수로 표시하는 것은 2진수처럼 16의 제곱수를 외워서하던가 나눗셈을 하면 됩니다. 90 | 91 | 참고로 16진수나 2진수를 10진수로 바꿀 일은 별로 없습니다. 16진수와 2진수를 서로 바꿀 일은 많습니다만 10진수로 변환하려고 나눗셈을 몇번씩 하는 일은 드뭅니다. 가끔 필요할 때는 계산기를 이용하면 됩니다.(주2) 92 | 93 | ## 부호의 표시 94 | 95 | 지금까지 음수는 생각하지 않았습니다. 사실 컴퓨터는 음수를 인식하지 못합니다. 8비트의 연산을 생각해보겠습니다. 96 | 97 | 0FFh는 255일까요 아니면 -1일까요? 8비트 연산이라는 것은 0과 1이 8개 있다는 것입니다. 2진수로 0000000b에서 1을 빼면 값이 뭐가 될까요? 반대로 생각해보면 어떤 수에 1을 더하면 0이 될까요? 98 | 99 | 정답은 11111111b입니다. 여기에 1을 더하면 00000000b가 됩니다. 8비트 연산이므로 원래는 100000000b이 되야하지만 1이 저장되지못하고 사라지는 것입니다. 그래서 -1은 11111111b입니다. 그럼 -2는 11111110b이고 -3은 11111101 입니다. -2에 2(00000010)를 더해보고 -3에도 3(00000011)을 더해보세요. 이렇게 계속 하다보면 -127은 10000001b입니다. 100 | 101 | 한가지 이상한건 -128은 10000000b이라는 겁니다. 양수로 128도 10000000b인데요. 여기서 우리는 한가지 약속을 합니다. MSB 즉 가장 왼쪽 비트가 1일때를 음수로 생각하자는 것입니다. 102 | 103 | 그래서 8비트의 연산에서 양수는 00000000~01111111입니다. 0부터 127입니다. 104 | 105 | 음수는 11111111~10000000입니다. -1부터 -128입니다. 106 | 107 | 8비트 00000000에 1을 계속 더해보세요. 0부터 시작해서 1, 2, 3 ... 127이고 그 다음은 -128이고 그 다음은 -127 그 다음은 -126 ... -1 그리고 다시 0이 됩니다. 108 | 109 | 이렇게 이상한 음수 표시가 있을 수 있는 것은 연산의 범위가 8비트로 제한되기 때문입니다. 일반적인 수학에서 자릿수가 제한되지는 않지요. 자리수는 무한대까지 늘어납니다. 하지만 컴퓨터에서는 프로세서가 저장할 수 있는 비트의 수가 제한됩니다. 16비트 컴퓨터 8086은 16비트까지만 저장할 수 있습니다. 즉 자릿수가 무한대가 되지 않고 숫자가 계속 증가하다가 다시 0이 되는 순간이 오기 때문에 최상위 비트 MSB가 1일 때를 음수로 정할 수 있습니다. 110 | 111 | 이상하게 생각되시면 8비트 숫자 뭐든지 계산을 해보세요. 이상하게 들어맞습니다. 처음에는 이상할 수 있습니다. 어셈블리 프로그래밍을 오래하다보면 생기는 병중에 숫자의 시작을 0으로 생각하는 것과 127다음이 -128이라고 생각해서 덧셈을 이상하게 하는 것이 있습니다. 112 | 113 | --- 114 | 115 | 주1: 플립플롭이라는 디지털회로입니다. 디지털회로까지 알고싶으시면 전자공학까지 배워야합니다. 우리는 논리회로만 생각합니다. 논리회로에서는 1/0(참/거짓)만 생각합니다. 116 | 117 | 주2: 윈도우나 리눅스나 모두 계산기에 프로그래머용 모드가 있습니다. 16,10,8,2진수의 계산을 쉽게 할 수 있습니다. 또 윈도우의 계산기에는 Byte, Word, Dword, Qword 모드가 있습니다. 각각 8비트, 16비트, 32비트, 64비트를 의미합니다. 118 | -------------------------------------------------------------------------------- /8086arch.md: -------------------------------------------------------------------------------- 1 | # 8086의 기본 구조 2 | 3 | 어셈블리 언어를 보통 가장 낮은 레벨의 언어라고 말합니다. 여기에서 높은 언어란 사람들끼리 사용하는 자연어를 말합니다. 그리고 낮을 수록 기계에 가깝다는 뜻입니다. 어셈블리 언어는 인간이 사용할 수 있는 언어중에서는 가장 기계에 가깝다는 말이 됩니다. 따라서 어셈블리 언어로 뭔가를 해보려면 기계를 먼저 알아야합니다. 어셈블리 언어보다 먼저 수와 기계를 설명하는게 그런 이유 때문입니다. 이번 글에서는 8086이 어떤 구조로 되어있고 어셈블리 언어로 조종할 수 있는 기능들이 뭐가 있나 알아볼 것입니다. 4 | 5 | 어셈블리 언어보다 더 기계에 가까운게 있을까요? 숫자가 있습니다. 모든 어셈블리 언어는 기계가 이해할 수 있는 또 기계를 직접 동작시키는 숫자로 변환됩니다. 그렇게 변환하는 것을 어셈블러 assembler 변환하는 작업 자체를 어셈블링 assembling 이라고 합니다. 숫자의 모음은 기계어라고도 합니다. 사람의 언어가 아니라는 것이지요. 그래서 인간이 사용할 수 있는 최소한의 언어가 어셈블리 언어입니다. 6 | 7 | 참고로 어셈블리 언어는 숫자와 1대1로 매칭됩니다. 아직 배우지는 않았지만 mov라는 명령어가 있는데 mov라는 명령이 나오면 이 명령은 항상 특정 숫자로 변환됩니다. 이 값을 123h라고 가정해보면 어셈블러는 코드에서 mov를 만나면 그대로 123h라고 변환하는 것입니다. 그리고 mov ax, bx라는 명령을 만나면 항상 12345h라고 변환한다고 해봅시다. 어셈블러는 컴파일러에 비하면 굉장히 간단한 프로그램입니다. 8 | 9 | ![](/assets/model.gif) 10 | 11 | 이 그림은 컴퓨터를 아주아주 많이 간단하게 표현한 것입니다. 메모리와 프로세서, 장치들이 버스로 연결되어있습니다. 우리는 파일을 하드디스크에 저장합니다. 그리고 더블클릭을 하면 프로세서가 더블클릭이라는 명령을 이해하고 파일을 하드디스크에서 메모리로 읽어옵니다. 그리고 메모리를 조금씩 읽어서 파일에 써있는 명령어들을 실행합니다. 그 명령어가 특정 이미지를 장치로 보내라는 명령이면 영상 출력이 되는 것이고 특정한 데이터를 스피커로 보내라는 것이면 음악 출력이 되는 것입니다. 12 | 13 | ## 프로세서 14 | 15 | 우리가 어셈블러로 우리 어셈블리 언어 코드를 어셈블링해서 저장한 실행파일이 있다면 이 파일은 하드디스크 장치에 저장될 것입니다. 그리고 프로세서는 실행파일을 메모리로 옮기고 메모리에서 명령들을 읽어서 실행할 것입니다. 8086 프로세서의 구조를 간단한게 생각해보면 이렇습니다. (주1)(주2) 16 | 17 | ![](/assets/cpu.gif) 18 | 19 | ## 범용 레지스터 20 | 21 | 레지스터라는 것은 프로세서 안에 있는 16비트의 저장공간입니다. 메모리와 역할이 같지요. 하지만 메모리와 프로세서는 프로세서보다 훨씬 느린 버스라는 전선으로 연결되어있어서(주3) 메모리에 있는 데이터를 프로세서가 읽으려면 프로세서는 한참을 기다려야 합니다. 하지만 레지스터는 프로세서 내부에 있기 때문에 아주 빠르게 읽을 수 있습니다. 속도 차이는 최소한 만배 이상입니다. 마치 서울에서 대전에가서 물건을 사오는 것과 주머니 속에있는 물건을 꺼내오는 것의 차이와 같습니다. 대전에 가서 큰 물건을 사올 수는 장점이 있지만 시간이 오래 걸린다는 단점이 있지요. 주머니속 물건도 빨리 꺼낼 수는 있지만 큰 물건을 넣을 수는 없다는 단점이 있습니다. 22 | 23 | 그래서 계산할 때 최대한 많은 데이터를 메모리에서 레지스터로 읽어오고 최대한 레지스터만 가지고 계산을 합니다. 그리고 최종 데이터만 메모리에 기록하도록 하면 프로그램 속도를 높일 수 있습니다. 그렇게 계산 중간 중간에 사용할 임시 데이터를 저장하는 공간을 레지스터라고 생각하면 됩니다. 24 | 25 | 8086에는 8개의 범용 레지스터가 있습니다. 26 | 27 | AX: 누산 레지스터라고 불리거나 연산 레지스터라고 불립니다. 계산에 주로 사용됩니다. 28 | 29 | BX: 베이스 주소 레지스터라고 불립니다. 메모리 주소를 계산할 때 사용됩니다. 30 | 31 | CX: 카운터 레지스터라고 불립니다. 반복문에서 지금 몇번째로 반복하고 있는지를 기억할 때 사용됩니다. 32 | 33 | DX: 데이터 레지스터입니다. 계산의 결과 값을 저장하거나 메모리에서 읽어온 데이터를 저장합니다. 34 | 35 | SI: 소스 인덱스 레지스터(Source Index Register)라고 불립니다. 메모리 복사 등에서 원본 데이터의 주소를 저장할 때 사용합니다. 36 | 37 | DI: 목적지 인덱스 레지스터(Destination Index Register)라고 불립니다. 메모리 복사 등에서 목적지의 주소를 저장할 때 사용합니다. 38 | 39 | BP: 베이스 포인터 레지스터(Base Pointer Register)입니다. 스택 주소를 보존하는데 사용됩니다. 40 | 41 | SP: 스택 포인터 레지스터(Stack Pointer Register)입니다. 현재의 스택 주소가 저장됩니다. 42 | 43 | 각각의 레지스터의 기능을 간단히 설명했지만 지금은 아마 전혀 이해가 안되실 겁니다. 각각의 레지스터의 사용법을 emu8086으로 직접 실습해봐야 어떤 레지스터를 어디에 어떻게 사용하는 것인지 알게됩니다. 지금은 그냥 이런 이름들의 레지스터가 8개 있다는 것만 아시면 됩니다. 44 | 45 | 그리고 각각의 레지스터에 용도가 있다고 말씀드렸지만 이건 그냥 일반적인 용도를 말한 것이고 사실은 프로그래머가 마음대로 사용해도 됩니다. CX를 계산에 사용해도 되고 BX에 루프 카운터를 저장해도 됩니다. 46 | 47 | 모든 레지스터는 16비트입니다. 그리고 특별히 AX, BX, CX, DX는 8비트로 나눠서 반씩만 사용할 수 있습니다. 어떤 계산을 할 때 이 계산이 굳이 16비트가 필요없는 계산이라면 8비트만 사용할 수 있습니다. 이것도 emu8086으로 실습해보겠습니다. 48 | 49 | 만약에 AX 레지스터에 1234h라고 값을 쓰면 어떻게 될까요? AH에 12h가 저장되고 AL에 34h가 저장될까요? 아니면 AL에 12h가 저장되고 AH에 34h가 저장될까요? 실습을 하면서 알아보겠습니다. 일단 지금은 16비트의 레지스터가 있고 8비트로 나눠서 사용할 수도 있다라는 것만 알고 넘어가겠습니다. 50 | 51 | 왜 레지스터는 작을까요? 빠르니까 크게 만들면 좋을 텐데요. 이유는 돈때문입니다.프로세서에 뭔가를 넣는다는 것은 돈이 많이 들지요. 컴퓨터를 조립해보면 캐시 메모리가 큰 프로세서가 더 비싼데 캐시 메모리가 프로세서 안에 있기 때문입니다. 레지스터도 크면 클수록 프로세서가 비싸집니다. 52 | 53 | ## 세그먼트 레지스터 54 | 55 | 아직 설명안한 나머지 레지스터들이 세그먼트 레지스터입니다. 마찬가지로 그냥 그런 것들이 있다라고만 이해하고 다음에 실습을 하면서 어떤 역할을 하는지 알아가기로 하겠습니다. 여기서는 소개만 하겠습니다. 56 | 57 | CS: 현재 프로그램이 저장된 세그먼트의 주소가 저장됩니다. 58 | 59 | DS: 현재 사용하는 데이터가 저장된 세그먼트의 주소가 저장됩니다. 60 | 61 | ES: 별도의 용도는 없고 필요할 때마다 원하는 메모리 위치의 세그먼트 주소를 저장합니다. 62 | 63 | SS: 스택이 있는 세그먼트의 주소가 저장됩니다. 64 | 65 | 세그먼트라는 것은 좀더 많은 메모리를 컴퓨터에 넣기 위해 생겨난 개념입니다. 예를 들어서 설명하면 더 쉬울것 같습니다. 66 | 67 | 8086에서는 모든 레지스터가 16비트입니다. 따라서 메모리 주소를 저장하는 레지스터도 16비트입니다. 16비트는 2^16이므로 2^10 * 2^6 = K * 64 = 64K가 됩니다. 지금 보통 컴퓨터 메모리가 1G나 2G를 쓰는데요 우리가 쓰는 프로세서의 레지스터 크기가 32비트이기때문에 4G까지 메모리 주소를 계산할 수 있기 때문입니다. 8086은 16비트이기때문에 64K가 한계이지요. 68 | 69 | 그런데 64K는 너무 작습니다. 그 옛날에도 메모리는 무조건 클 수록 좋았던 것이지요. 그래서 고민한 결과가 두개의 레지스터를 붙여서 좀더 메모리의 접근 범위를 넓혀보자입니다. 두개를 붙이면 32비트가 되겠지만 그당시 기술로는 그렇게까지 크게 만들지 못하고 단지 한 자리만 더 늘릴 수 있었습니다. 이렇게요. 70 | 71 | DS 레지스터에 1230h을 저장하고 SI 레지스터에 45h를 저장합니다. 그러면 실제로 접근하려는 메모리의 주소가 1230h * 10h + 45h = 12300h + 45h = 12345h 가 됩니다. 72 | 73 | 즉 16비트 레지스터가 1234h같이 4자리 16비트의 주소만 표시할 수 있는데 세그먼트 레지스터 하나를 더 써서 5자리 20비트의 주소를 표시할 수 있게 만듭니다. 그러면 2^20이 되니까 1M의 메모리를 프로세서에 연결할 수 있게 됩니다. 74 | 75 | 세그먼트 레지스터의 역할이 이해가 되시나요? 주소 범위를 넓혀주는 것이라고 생각하시면 됩니다. 76 | 77 | 다시한번 예를 들면 반대로 12345h 라는 주소를 8086 레지스터에서 표현하려면 세그먼트 레지스터에 1230h를 저장하고 SI 레지스터에 45h를 저장하면 됩니다. 특이한 점은 세그먼트 레지스터에 1234h를 저장하고 SI 레지스터에 5h를 저장해도 같은 주소가 됩니다. 어떻게 하든지 프로그래머 마음입니다. 단지 두개의 레지스터의 합만 원하는 값이면 됩니다. 78 | 79 | 살짝 세그먼트 레지스터와 SI/DI 레지스터의 역할을 설명했습니다만 아직 잘 이해가 안되실 겁니다. 실습을 해봐야 이해가 되실 거니까 실습을 기다려 주세요. 80 | 81 | 지금은 메모리 주소가 20비트이고 하나의 레지스터가 16비트이므로 메모리 주소를 표현하기 위해서는 두개의 레지스터를 붙여서 표현해야한다는 것입니다. 그리고 그 때 사용하는 레지스터가 CS, DS, ES 라는 세그먼트 레지스터와 SI, DI라는 인덱스 레지스터라는 것만 알고 넘어가겠습니다. 82 | 83 | ## 특수 목적 레지스터 84 | 85 | 아직 설명안한 레지스터가 있습니다. 아무리 어셈블리 언어라고해도 직접 값을 쓰고 읽을 수 없는 특수한 레지스터입니다. 86 | 87 | IP: 명령어 포인터 Instruction Pointer 레지스터입니다. 현재 수행할 명령어의 주소를 나타냅니다. 88 | 89 | 플래그 레지스터: 프로세서의 상태를 나타냅니다. 90 | 91 | 이 두개의 레지스터는 프로그래머가 마음대로 값을 바꿀 수 없습니다. 프로그램이 실행되면 프로세서가 자동으로 값을 바꾸는 레지스터입니다. 그래서 두개다 프로세서의 상태를 알려주는 레지스터입니다. 92 | 93 | 각 레지스터의 역할도 실습을 하면서 알아보겠습니다. 94 | 95 | 96 | 97 | 이번 글은 레지스터의 소개를 했습니다. 많은 레지스터가 소개되어서 어려우실 수 있습니다만 지금은 8086에 여러개의 레지스터가 있고 각 레지스터가 16비트라는 것, 그리고 메모리 주소를 나타내기위해 두개의 레지스터를 붙여서 20비트의 값을 사용한다는 것만 확실히 아시면 됩니다. 각 레지스터들은 어셈블리 언어로 프로그래밍을 하면서 하나씩 직접 써보면서 친해지도록 하겠습니다. 98 | 99 | 100 | --- 101 | 102 | 103 | 주1: 이 그림은 프로세서를 논리적인 기능들로 나타낸 것입니다. 물리적으로 어떻게 프로세서를 만드는지는 전혀 다른 이야기입니다. 104 | 105 | 주2: 8086이 최초의 현대적인 프로세서라고 불리는 이유는 이 그림에 나온 AX, BX 등의 범용 레지스터나 CS, IP 등의 제어 레지스터 등 8086에 있는 모든 것이 지금 현재의 i5같은 프로세서에도 그대로 있기 때문입니다. 8086 이후로 핵심적인 기능들은 변하지 않고 호환성을 유지하고 있기 때문에 우리가 8086을 배우는 것이기도 합니다. 106 | 107 | 주3: 메인보드의 판때기?를 보시면 녹색 선들이 있지요? 그것들이 사실은 전선입니다. 전기가 통하는 선인데 아주 얇게 만들어서 메인보드의 판속에 뭍어둔 것입니다. 108 | -------------------------------------------------------------------------------- /functioncalling.md: -------------------------------------------------------------------------------- 1 | #함수 호출 규약과 지역변수 2 | 3 | 이전에는 함수 호출을 위한 call,ret 명령에 대해서만 알아보기위해 개략적인 내용만 설명했습니다. 이제는 함수가 어떻게 호출되는지 제대로 알아보겠습니다. 함수를 호출할 때는 스택을 활용합니다. 스택이 어떻게 사용되는지를 설명하겠습니다. 4 | 5 | ##함수 호출 6 | 7 | 함수를 배울 때 실습했던 예제가 있었습니다. 레지스터에 함수 인자를 저장하고 함수를 호출해서 함수내에서 곱셈을 실행하고 결과값을 ax 레지스터로 반환하는 예제입니다. 이 예제를 다음처럼 스택을 이용해서 인자를 전달하도록 바꿔보겠습니다. 사실은 이 예제처럼 스택에 인자를 저장하고 함수 내부에서는 스택 메모리에 접근해서 인자를 읽는 방식이 C의 표준적인 함수 호출 규약중 하나입니다. 이 규약을 잘 지키면 어셈블리 코드에서 libc의 라이브러리 함수를 호출하는 것도 가능합니다. 8 | 9 | 일단 에물레이터로 실행해보시기 바랍니다. 10 | ``` 11 | ORG 100h 12 | 13 | MOV dx, 1 14 | push dx 15 | MOV dx, 2 16 | push dx 17 | CALL m2 18 | add sp, 4 19 | 20 | push ax 21 | mov dx, 2 22 | push dx 23 | call m2 24 | add sp, 4 25 | 26 | RET ; return to operating system. 27 | 28 | m2 PROC 29 | 30 | mov bp, sp 31 | mov bx, [bp+2] 32 | mov ax, [bp+4] 33 | 34 | MUL Bx ; AX = AL * BL. 35 | RET ; return to caller. 36 | m2 ENDP 37 | 38 | END 39 | ``` 40 | 좀 복잡하지만 에물레이터로 한줄한줄 실행해보면 어렵지 않습니다. 41 | 42 | 가장 먼저 dx에 1을 저장한 후 스택에 저장합니다. 꼭 dx 레지스터를 사용할 필요는 없습니다. 그냥 스택에 1을 저장하기 위해 아무 레지스터나 사용한 것입니다. 그리고 스택에 2를 저장합니다. 이제 함수에 전달할 인자 1과 2가 스택에 저장되었습니다. 인자를 준비했으니 함수 m2를 호출합니다. 43 | 44 | 에물레이터에서 stack 버튼을 눌러서 스택의 상황을 보고 계신가요? 그럼 call 명령이 호출된 후에 스택에 내가 저장하지 않은 이상한 값이 들어간 것을 볼 수 있습니다. 스택 메모리 0fffch에 1이 저장되고 0fffah에 2가 저장되고 그리고 0fff8h에 10bh 값이 저장되었을 것입니다. 10bh값이 뭔지는 잠시 후에 말씀드리기로 하고 지금은 함수 인자가 스택에 있다는 것만 기억하시기 바랍니다. 45 | 46 | 이제 함수에서 함수 인자를 읽어야 합니다. 그런데 pop 명령을 사용하면 스택에 있는 10bh 값이 읽혀집니다. 뭔가 역할이 있는 값일테니 pop 명령을 써서 날려버리면 안되겠지요. 그래서 스택을 건드리지않고 메모리에 있는 값만 읽도록 해야합니다. 이럴 때는 주로 bp 레지스터를 씁니다. bp 레지스터는 그냥 변수 주소를 넣을 때도 쓸 수 있지만 사실은 이렇게 스택에 있는 함수의 인자를 읽기 위해서 스택의 포인터의 복사본을 저장하는데 주로 사용됩니다. [bp]로 메모리의 값을 읽으면 10bh 값이 읽어질거니까 그건 건너뜁니다. 그래서 함수의 인자는 [bp+2] 와 [bp+4]가 됩니다. 47 | 48 | 혹시 굳이 bp를 사용하지 않고 [sp+2], [sp+4]로 읽어도 된다고 생각하지 않으시나요? bp를 사용하는 이유는 지역변수를 이야기할 때 말씀드리겠습니다. 무조건 bp를 사용해서 함수 인자를 읽는다고 생각하셔야 합니다. 49 | 50 | 함수의 첫번째 인자는 [bp+4]이고 두번째 인자는 [bp+2]입니다. 처음 스택에 넣은 데이터가 좀더 큰 주소에 있습니다. 두개의 인자를 적당히 읽어서 함수가 해야하는 처리를 합니다. 예제에서는 곱셈입니다. 그리고 곱셈의 결과가 ax에 저장됩니다. 51 | 52 | 이제 함수가 끝납니다. ret 명령이 함수의 끝에 반드시 있어야 된다고 말씀드렸고 ret 명령이 실행되면 call 명령의 다음 명령으로 점프한다고 말씀드렸습니다. 에물레이터에서 ret 명령을 single step으로 실행하고 sp 레지스터의 값을 확인해보겠습니다. 0fff8h였던 sp의 값이 0fffah가 되었습니다. 즉 ret 명령은 스택에서 10bh 값을 꺼내는 일을 합니다. 그리고 ip 값을 보시면 10bh 가 되어있습니다. 10bh는 어디인가요? call m2 다음 명령인 add sp, 4 명령이 있는 위치입니다. 즉 call 명령은 자기 다음의 명령의 주소를 스택에 저장하고 ret 명령은 스택에서 복귀 주소를 꺼내서 실행하는 것입니다. 이렇게 call 명령과 ret 명령이 함수를 호출하고 복귀하는 것입니다. 별로 어렵지 않은 것을 좀 어렵게 설명한 기분이지만 기분탓입니다. 53 | 54 | 그리고 함수가 끝나고 해야할 일은 스택에 있던 인자들을 지워주는 것입니다. 인자들을 지우지 않으면 함수를 호출 할 때마다 스택이 점점 작아지겠지요. pop 을 두번해서 스택을 되돌리는 방법도 있고 예제처럼 sp 레지스터에 4를 더해서 sp의 값을 예전 값으로 되돌리는 방법도 있습니다. 이왕이면 2개보다는 1개 명령이 실행되는게 좋겠지요. 55 | 56 | 또다시 m2를 호출하겠습니다. 이번에는 이전의 결과값이 ax에 있으므로 ax를 스택에 넣습니다. 그리고 다시 2를 넣습니다. 그리고 m2를 호출하면 ax에는 2*2=4가 저장되겠네요. 마지막으로 다시 스택을 복구시킵니다. 57 | 58 | 스택은 이렇게 함수 호출에 이용됩니다. 심심하신 분들은 C로 무한 재귀 함수를 만들어보시기 바랍니다. 스택 복구하는 코드가 실행되지 않고 계속 스택을 사용하기만 하므로 스택 영역을 모두 사용해서 세그먼트 폴트가 발생합니다. 스택을 몰랐다면 함수가 무한히 재귀적으로 실행되도 무한히 실행되고 문제가 없을것 같은데 그게 아니라메모리 문제가 발생한다는 것을 알 수 있습니다. 59 | 60 | 61 | 62 | ##함수의 지역 변수 63 | 64 | 이제 함수 내부에서 인자에 접근하기 위해 왜 sp가 아닌 bp를 사용하는지 말씀드리겠습니다. 바로 함수의 지역 변수를 스택에 만들기 때문입니다. 65 | 66 | 다음 예제는 인자로 받은 값을 swap해주는 함수입니다. swap함수는 무조건 지역 변수가 하나 있어야 된다는거 아시지요? 67 | ``` 68 | ORG 100h 69 | 70 | lea ax, var1 ; push &var1 71 | push ax 72 | lea bx, var2 ; push &var2 73 | push bx 74 | CALL swap 75 | add sp, 4 76 | 77 | ret 78 | 79 | var1 dw 11h 80 | var2 dw 22h 81 | 82 | 83 | swap PROC 84 | 85 | mov bp, sp 86 | mov si, [bp+2] ;si = &var2 87 | mov di, [bp+4] ;di = &var1 88 | 89 | mov ax, 0 ; temp=[bp-4] 90 | push ax 91 | 92 | mov ax, word ptr [si] ; ax = var1 93 | mov bx, word ptr [di] ; bx = var2 94 | mov word ptr [bp-2], ax ; temp = var1 95 | mov word ptr [si], bx ; *&var1 = var2 96 | mov dx, word ptr [bp-2] ; dx = temp 97 | mov word ptr [di], dx ; *&var2=temp 98 | 99 | mov sp, bp 100 | 101 | RET ; return to caller. 102 | swap ENDP 103 | ``` 104 | 105 | 106 | 두개의 변수 var1, var2를 만듭니다. 각각 11h, 22h 값이 들어있습니다. 이제 swap을 호출해서 22h, 11h로 값을 바꾸겠습니다. 값을 바꾸려면 swap 함수에 변수의 값을 넘겨야할까요 아니면 변수의 주소를 넘겨야 할까요? 당연히 주소를 넘겨야합니다. C 언어를 모르셔서 주소를 넘기는 이유를 모르시는 분들은 그냥 그렇다고만 생각하셔도 좋습니다. 107 | 108 | swap 함수가 시작됩니다. bp에 스택 주소를 저장합니다. 함수 인자는 변수의 주소입니다. 따라서 [bp+2]는 var2의 주소이고 [bp+4]는 var1의 주소입니다. 각각 읽어서 si와 di에 저장합니다. 109 | 110 | 그리고 스택에 0을 집어넣습니다. 값이 0인 것은 중요하지 않습니다. 단지 스택에 공간을 하나 만든 것입니다. 그리고 이게바로 함수의 지역 변수입니다. C언어를 아시는 분은 지역 변수의 정의를 생각해보시면 왜 스택에 지역변수를 저정하는지 아실겁니다. 지역 변수는 함수 내부에서만 사용하다가 함수가 끝나면 사라지는 변수입니다. 따로 메모리를 할당하고 해지할 필요가 없는 특징이 있습니다. 바로 스택 메모리의 특징과 같습니다. 스택에 지역 변수를 저장하므로 그런 특징들이 생겨난 것입니다. 111 | 112 | sp 의 값은 지역 변수를 만들 때마다 계속 바뀔 것입니다. 따라서 함수가 호출된 직후에 초기 sp의 값을 저장해놓았다가 함수가 끝났을 때 복구해야합니다. 그래야 ret 명령으로 복귀 주소를 읽을 수가 있습니다. 그래서 초기 sp의 값을 bp에 저장해놓는 것입니다. 그리고 bp는 항상 일정한 값이므로 bp를 기준으로 +를 하면 함수 인자를 읽게되고 -를 하면 지역 변수를 읽을 수 있습니다. [bp]를 그대로 읽으면 복귀 주소가 되겠지요. 결국 [bp-2]가 지역 변수가 됩니다. 113 | 114 | si에서 워드값을 읽으면 var1 변수의 값입니다. di에서 읽으면 var2의 값입니다. 그리고 지역 변수 [bp-2]에 var1 값을 저장합니다. 여기까지가 temp = a 동작입니다. C로 한줄이면 되는게 몇줄이 되버립니다. 115 | 116 | si에는 var1의 주소가 있으므로 mov word ptr [si], bx 명령으로 var1의 값을 바꿉니다. 그리고 dx에 지역 변수에 저장했던 var1의 값을 읽어와서 [di]에 저장합니다. 그러면 var2의 값이 바뀌는 것입니다. 117 | 118 | 마지막으로 sp를 bp로 바꾸면 스택이 초기화되고 복귀 주소를 꺼낼 수 있게 됩니다. 119 | 120 | 좀 복잡하지만 뭔가 생각나는게 많으시리라 믿습니다. C 언어의 포인터가 뭔지 간접 레퍼런스니 하는 개념들이 뭔지 등등 C의 주요 개념들을 다시한번 되새겨보시는 기회가 되었으면 합니다. 121 | 122 | 123 | 124 | ##레지스터 복구 125 | 126 | swap 프로그램에서 만약에 swap을 2번 호출한다면 어떻게 해야할까요? 127 | ``` 128 | ORG 100h 129 | 130 | lea ax, var1 ; push &var1 131 | push ax 132 | lea bx, var2 ; push &var2 133 | push bx 134 | CALL swap 135 | add sp, 4 136 | 137 | lea ax, var1 ; push &var1 138 | push ax 139 | lea bx, var2 ; push &var2 140 | push bx 141 | CALL swap 142 | add sp, 4 143 | 144 | ret 145 | ``` 146 | 이렇게 똑같은 호출을 2번 하면 될까요? 그래도 되긴 합니다. 하지만 ax, bx 레지스터에 값을 설정하는 부분이 중복됩니다. 따라서 중복 부분을 없애면 더 좋겠지요. 147 | ``` 148 | ORG 100h 149 | 150 | lea bx, var2 ; push &var2 151 | 152 | lea ax, var1 ; push &var1 153 | 154 | push ax 155 | push bx 156 | CALL swap 157 | add sp, 4 158 | 159 | push ax 160 | push bx 161 | CALL swap 162 | add sp, 4 163 | 164 | ret 165 | ``` 166 | ax, bx를 설정하고 필요할 때마다 함수 호출을 반복하면 더 좋을것 같습니다. 그런데 막상 실행해보면 안됩니다. 왜냐면 첫번째 swap이 ax, bx 의 값이 바꾸기 때문입니다. 함수들은 항상 레지스터를 쓰기 마련인데 그렇게 되면 함수 호출 이전에 있는 레지스터의 값들이 날아가 버립니다. 따라서 라이브러리 함수일 경우 호출된 다음에 레지스터가 어떻게 바뀌었는지 모르게 될 수도 있고, 내가 만든 함수라도 일일이 함수 호출 전에 레지스터의 값들을 스택에 넣어줘야하는 불편도 있습니다. 167 | 168 | 그래서 이런 불편을 없애기 위해 모든 함수는 자기가 사용할 레지스터들을 스택에 보존했다가 함수 종료 직전에 복구하는 규칙이 있습니다. 169 | 170 | swap을 다시 만들어보겠습니다. swap에서는 bp, si, di, ax, bx, dx를 사용합니다. 따라서 함수 초기에 이런 레지스터들을 스택에 보존하는 일을 해야합니다. 171 | 172 | ``` 173 | swap PROC 174 | 175 | mov bp, sp 176 | 177 | mov ax, 0 ; temp=[bp-4] 178 | push ax 179 | 180 | push bp 181 | 182 | push si 183 | 184 | push di 185 | 186 | push ax 187 | 188 | push bx 189 | 190 | push dx 191 | 192 | mov si, [bp+2] ;si = &var2 193 | 194 | mov di, [bp+4] ;di = &var1 195 | 196 | mov ax, word ptr [si] ; ax = var1 197 | 198 | mov bx, word ptr [di] ; bx = var2 199 | mov word ptr [bp-2], ax ; temp = var1 200 | mov word ptr [si], bx ; *&var1 = var2 201 | mov dx, word ptr [bp-2] ; dx = temp 202 | mov word ptr [di], dx ; *&var2=temp 203 | 204 | pop dx 205 | 206 | pop bx 207 | 208 | pop ax 209 | 210 | pop di 211 | 212 | pop si 213 | 214 | pop bp 215 | 216 | pop ax 217 | 218 | mov sp, bp 219 | 220 | RET ; return to caller. 221 | swap ENDP 222 | ``` 223 | 224 | 이렇게 각 함수들이 자기들이 사용할 레지스터의 값들을 복구시켜주면 함수를 호출하는 입장에서는 함수가 무슨 레지스터를 사용할지 신경쓸 필요가 없으므로 편리합니다. 225 | 226 | 이렇게 함수 호출할때 인자 전달 방법, 지역 변수 생성과 레지스터의 복구까지가 함수 호출 규약에 정해져있는 것들입니다. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | {one line to give the program's name and a brief idea of what it does.} 635 | Copyright (C) {year} {name of author} 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | {project} Copyright (C) {year} {fullname} 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | --------------------------------------------------------------------------------