'공부합시다'에 해당되는 글 70건

  1. 2009.05.19 [프로그래밍 일반] PE구조란?
  2. 2009.05.14 [어셈블리어] push와 pop의 구분동작
  3. 2009.05.14 [어셈블리어] PUSHA와 POPA
  4. 2009.05.11 [어셈블리어] 데이터 전송 명령어 mov, movzx, movsx
  5. 2009.05.08 [어셈블리어] 곱셈 - MUL, IMUL
  6. 2009.05.06 [어셈블리어] 나눗셈
  7. 2009.05.06 [어셈블리어] data 세그먼트와 bss 세그먼트
  8. 2009.05.06 [C언어] 부호 확장(Sign extension)
  9. 2009.04.30 [C언어] 구조체의 메모리 저장방식과 #pragma pack
  10. 2009.04.28 [ATmega128] RS232 시리얼 통신 관련 레지스터 정리
2009. 5. 19. 15:17

[프로그래밍 일반] PE구조란?

 PE(Portable Excutable)란 1993년 Microsoft사에서 표준화한 형식으로 이식성이 좋은(Portable) 실행구조(Excutable)를 의미하며 32bit 혹은 64bit의 Window OS에서 사용되는 실행 File, Object code, DLL등이 이러한 구조를 갖는다.

 PE는 API Export, Import table, data를 관리하기 위한 Resource, TLS(Thread Local Storage)들이 포함된 구조체로 이루어져 있다.

 그림에서 SESSION 영역을 보면 여러 영역들이 있는데 이중 전역변수는 .data영역에 위치하며 output 함수처럼 사용자가 만든 함수들은 .text영역에 들어간다 그리고 지역변수는 TLS에 들어가게 된다. 이처럼 PE구조에는 실행file에서 필요한 모든 정보들이 들어가 있다.

2009. 5. 14. 17:27

[어셈블리어] push와 pop의 구분동작

push eax
 1. --esp
 2. mov [esp], eax

스택 포인터를 하나 감소 시키고(즉, 스택포인터를 올리고) 값을 넣는다.(스택은 아래서부터 저장되므로 스택이 쌓일수록 스택 포인터가 가르키는 주소의 숫자는 감소한다.)


pop eax
 1. mov eax, [esp]
 2. ++esp

 스택 포인터가 가르키는 메모리 주소의 값을 레지스터에 저장하고, 스택포인터를 증가 시킨다.(즉, 스택포인터를 내린다.)
2009. 5. 14. 14:07

[어셈블리어] PUSHA와 POPA

PUSHA : Push All
스택에 EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI 순으로 모두 저장한다.

POPA: Pop All
스택에서 EDI, ESI, EBP, EBX, EDX, ECX, EAX 순으로 꺼낸다.
(ESP는 복원하지 않는다.)
2009. 5. 11. 16:10

[어셈블리어] 데이터 전송 명령어 mov, movzx, movsx

MOV 명령

소스 피연산자로부터 도착점 피연산자로 데이터를 이동시킨다.

데이터 전송 명령으로 알려진 이 명령은 실질적으로 모든 프로그램에서 사용된다.

이 명령의 기본 포멧은 첫 번째 피연산자가 도착점이고 두 번째 피연산자가 소스이다.

MOV destination,source

오른쪽에서 왼쪽으로의 데이터 이동은 C++ 이나 자바에서의 할당 문과 유사하다

dest=source;

[거의 모든 어셈블리 언어 명령에서,

왼쪽 피연산자는 도착점이고,

오른쪽 피연산자는 소스이다.]

MOV는 피연산자의 사용에 있어서 다음의 규칙만을 따르면 된다.

1. 피연산자는 같은 크기여야 한다.

2. 피연산자는 모두 메모리 피연산자일 수 없다.

3. CS, EIP, IP는 도착점 피연산자가 될 수 없다.

4. 즉시값은 세그먼트 레지스터로 이동될 수 없다.

 

세그먼트 레지스터는 프로그램이 실제 주소 모드로 동작될 때만 사용할 수 있다.

CS가 도착점 피연산자로 사용될 수 없다는 것을 제외하고 다음과 같은 옵션이 가능하다.

MOV r/m16, sreg

MOV sreg,r/m16

 

메모리에서 메모리로 MOV 명령은 하나의 메모리 위치에서 다른 위치로 데이터를 이동시키는 데 사용할 수 없다.

대신에 도착점 피연산자로 이동하기 전에 소스 피연산자를 레지스터로 이동시킬 수 있다.

.data

var1 WORD ?

var2 WORD ?

.code

mov ax,var1

mov var2,ax

 

변수나 레지스터로 이동될 때 정수 상수가 몇 바이트가 필요한지를 먼저 고려해야만 한다.

 

작은 피연산자에서 큰 피연산자로 정수를 복사하기 위하여 MOV 명령을 사용하는 것은 오류를 발생시킨다.

그러나 때때로 그렇게 할 필요성이 있을 때가 있다.

count(부호가 없는 16비트)를 ECX(32비트)로 옮겨야 한다고 가정하자. 간단한 해결방법은 ECX를 0으로 지정하고 난 후 count를 CX로

옮기는 것이다.

.date

count WORD 1

.code

MOV ecx, 0

MOV cx, count

 

만약 부호가 있는 정수 -16을 같은 방법으로 시도한다면 어떤일이 일어나는가?

.data

signedVal SWORD -16                          ; FFF0h(-16)

.code

MOV ecx, 0

MOV cx, signedVal                              ; ECX = 0000FFF0h (+65520)

 

ECX(+65520)값은 -16 과 완전히 다른 값이다. 반면에, ECX를 FFFFFFFFh로 채운 후 signedVal을 CX에 복사한다면 마지막 값은 올바를 것이다.

 

MOV ecx, 0FFFFFFFFh                         //0FFFFFFFFh

MOV cx, sigendVal                              ; ECX=FFFFFFF0h

 

이것은 부호가 있는 정수를 다룰 때 문제를 야기한다. 도착점 피연산자를 어떻게 채울지 결정하기 위하여 그 수가 양수인지 음수인지를 점검해야 하는 것은 불편하다. 다행스럽게도 인텔 공학자들은 인텔 386 프로세서를 설계할 때 이 문제를 고려하였고 부호가 없는 정수와 부호가 있는 정수를 다루기 위한 명령어로 <OVZX와 MOVSX 명령어를 도입하였다.

예제)  aaa.asm 파일

TITLE move
INCLUDE Irvine32.inc
.data
             signedVal SWORD -16
.code
main PROC
             mov ecx,0FFFFFFFFh
             mov cx,signedVal
             call DumpRegs
             exit
main ENDP
END main

 

MOVZX 명령 (부호가 없는)

소스 피연산자를 도착점 피연산자로 복사하고 그 값을 16비트나 32비트로 확장한다. 이 명령은 부호가 없는 정수에만 사용된다.

3개의 변형이 가능하다.

 

MOVZX      r32, r/m8                                    //[32비트 범용 레지스터], [8비트 피연산자,8비트 범용레지스터나 메모리 바이트]

MOVZX      r32,r/m16

MOVZX      r16,r/m8

 

첫 번째 피연산자는 도착점이고 두 번째가 소스이다. 도착점은 레지스터여야만 한다.

다음 그림은 8비트 소스 피연산자가 제로 확장되어 16비트 도착점으로 이동하는 모습을 보여 준다.

 

 


다음 예는 피연산자가 모두 레지스터일 때 크기의 변형을 보여 준다.

MOV bx,0A69Bh

MOVZX EAX, BX                                     ; EAX = 0000A69Bh          //16비트 BX의 값이 이동

MOVZX EDX, BL                                     ; EDX = 0000009Bh           //8비트 BL(BX의 하위 8비트)값이 이동

MOVZX CX, BL                                       ; CX = 009Bh                   //8비트 BL값이 이동

 

다음의 에는 소스를 메모리 피연산자를 사용한 경우로서 같은 결과를 만든다.

 

.data

byte1 BYTE 9Bh

word1 WORD 0A69Bh

.code

MOVZX EAX, word1                       ; EAX = 0000A69Bh

MOVZX EDX, byte1                        ; EDX = 0000009Bh

MOVZX CX, byte1                          ; CX = 009Bh

 

MOVSX 명령 (부호가 있는)

 

소스 피연산자의 내용을 도착점 피연산자로 복사 후 16비트 혹은 32비트로 값을 부호 확장한다.

이 명령어는 부호가 있는 정수에만 사용된다. 3개의 변형이 있다.

 

MOVSX r32, r/m8

MOVSX r32, r/m16

MOVSX r16, r/m8

 

작은 피연산자의 최상위 비트를 취하고 이를 도착점 피연산자의 확장 비트에 반복적으로 복사하여 부호 확장을 한다.

예를 들어, 8비트 값 10001111b 가 16비트 도착점으로 이동한다면, 하위 8비트는 그대로 복사된다. 다음에 다음 그림에서

보는 것처럼 소스의 최상위 비트는 도착점의 상위 8비트에 복사된다.

 


다음의 예는 피연산자가 모두 레지스터일 때의 경우이다.

 

MOV BX, 0A69Bh

MOVSX EAX, BX                                       ; EAX = FFFFA69Bh

MOVSX EDX, BL                                       ; EDX = FFFFFF9Bh

MOVSX CX, BL                                         ; CX = FF9Bh

 

예제)  bbb.asm 파일

TITLE move
INCLUDE Irvine32.inc
.code
main PROC 
             MOV BX, 0A69Bh
             MOVSX EAX, BX
             MOVSX EDX, BL
             MOVSX CX, BL
             call DumpRegs 
             exit
main ENDP
END main

2009. 5. 8. 15:01

[어셈블리어] 곱셈 - MUL, IMUL

mul source

mul(부호없는 곱셈)
source 자리에는 레지스터나 주소가 올 수 있으며, 즉시값(상수)은 올 수 없다.
source에 1바이트가 오면 AL(1바이트)과 곱해서 AX(2바이트)에 저장된다. source에 2바이트가 오면 AX와 곱해서 DX:AX에 저장한다. source가 4바이트라면, EAX와 곱해서 EDX:EAX에 저장된다.
(1자리의 숫자 두개를 곱하면 최고 2자리의 숫자가 나올 수 있다. 따라서 1바이트를 2개 곱하면 2바이트의 저장공간이 필요하다.)



imul  dest, source1
imul  dest, source1, source2

imul(부호있는 곱셈)
dest
source1
source2
Action

reg/mem8

AX = AL*source1

reg/mem16

DX:AX = AX*source1

reg/mem32

EDX:EAX=EAX*source1
reg16
reg/mem16

dest *= source1
reg32
reg/mem32

dest *= source1
reg16
immed8

dest *= immed8
reg32
immed8

dest *= immed8
reg16 immed16

dest *= immed16
reg32 immed32

dest *= immed32
reg16 reg/mem16
immed8
dest=source1*source2
reg32 reg/mem32 immed8 dest=source1*source2
reg16 reg/mem16 immed16 dest=source1*source2
reg32 reg/mem32 immed32 dest=source1*source2

reg = 레지스터, mem = 메모리 주소내의 값, immed = 즉시값(상수)
2009. 5. 6. 17:28

[어셈블리어] 나눗셈

1. 나눗셈 연산의 피젯수는(32bit의 나눗셈을 가정) 항상 edx:eax 이다.
2. cdq 는 나눗셈을 위해 피젯수의 사이즈를 확장하는 것이다.


나눗셈연산(div, idiv)은 eax와 edx에 의해서만 이루어집니다.
- 피젯수(나눔을 당하는 수) 는 eax, edx에만 들어갈 수 있다는 얘기에요
16 / 5 연산을 한다고 가정해 봅시다.

16과 5 둘다 32bit data라고 가정하구요

그럼 일단 eax에 16을 넣습니다. 그 다음 ebx(다른레지스터나 메모리도 상관없음)에

5를 넣습니다. 그 다음 div 연산을 하면.........될것 같지만 안됩니다..

일반적으로 제수(여기서는 5)가 32bit이면 피젯수(여기서는 16) 는 64bit가 되어야

32bit 값을 가지는 몫을 얻을 수 있습니다.

그래서 피젯수의 bit를 확장 시켜주는것이 바로 cdq 연산입니다

32bit 크기의 eax의 값을 64bit의 값인 edx:eax로 만들어줍니다.

여기서 edx는 상위자리가되고 eax는 하위 자리가 되죠

자..그럼 cdq 연산까지 끝났으면 edx:eax에 16이 들어가있고 ebx에 5가 들어있겠네요

그럼 idiv연산을 해봅시다(div는 부호가없는 나눗셈 idiv 부호가 있는 나눗셈)

그럼 몫과 나머지가 나와야 하겠죠? 그 결과는 다시 eax와 edx로 들어가는데

eax에는 몫이, edx에는 나머지 부분이 들어갑니다.


출처: 내 심장이 0hz가 되는 순간까지...  (http://zerohz.tistory.com/61)

2009. 5. 6. 17:15

[어셈블리어] data 세그먼트와 bss 세그먼트

segment .data
msg    db    "Hello world", 0

segmen .bss
input    resd    1

위의 어셈블리어 코드를 보면, 두가지의 세그먼트를 선언하는 것을 볼 수 있다.

data segment에는 initialized data segment(초기화 된 데이터 세그먼트)와 uninitialized data segment(초기화 되지 않은 데이터 세그먼트)가 있는데 segment .data는 전자, segment .bss는 후자를 가르킨다.

즉, segment .data 에서는 초기값이 필요한 전역변수를 선언하고, segment .bss에서는 초기화가 필요없는 전역변수를 선언한다.

segment .bss에서는 RAM에 해당 영역만큼의 공간만을 잡아준다. 하지만 segment .data에서는 RAM을 할당할 뿐만 아니라 해당데이터의 초기 값을 지니고 있을 ROM또한 할당되어야 한다.

따라서, bss영역은 초기화 과정에서 컴파일러에서 지정한 초기화 방식에 의하여 초기화되며, data부분은 ROM에 있는 초기화된 데이터를 해당 data영역으로 복사함으로서 수행하게 된다.

즉, 위의 소스를 해석하면

msg라는 변수에 "Hello world\0"

2009. 5. 6. 15:53

[C언어] 부호 확장(Sign extension)



결과는 다음과 같다.



int형 b에 같은 -1을 대입했는데 왜 결과 값은 다르게 나올까.

그 이유는 부호 확장에 있다.

int b = a 에서 a는 char 형이기 때문에, 묵시적인 형변환이 발생하게 되는데 이때, char형 데이터인 a를 바로 int형 데이터 b에 넣는 것이 아니라 a를 4바이트 크기로 확장을 하게 된다.

1바이트에서 4바이트로 확장을 하면서 나머지 빈 공간에는 음수면 1로 채우고 양수면 0으로 채우게 된다.

따라서 (signed) char 인 a변수는 앞에 1을 채워서 FFFFFFFF가 되고, unsigned char 인 c변수는 앞에 0을 채워서 FF가 되는 것이다.
2009. 4. 30. 17:25

[C언어] 구조체의 메모리 저장방식과 #pragma pack

구조체는 메모리에 어떤 식으로 저장될까
다음과 같은 소스를 보자.


TEST 구조체 변수인 TData는 char형 데이터(1byte), short형 데이터(2byte), int형 데이터(4byte)를 가지고 있으므로 1+2+4=7byte의 크기를 가질 것 처럼 보인다. 하지만 이 소스를 컴파일하고 실행을 해보면 다음과 같은 결과가  나온다.



분명히 cData(1) + sData(2) + iData(4) = 7임에도 불고하고 TData의 크기는 8바이트라고 하니 참 이상한 일이다.

이는 현재 우리가 쓰는 32비트 컴퓨터에서 32비트 컴파일러를 사용하였기 때문에 32비트 즉, 4바이트로 데이터를 처리하는 것에 가장 최적화되어 있기 때문에 데이터를 4바이트 공간으로 저장하기 때문이다.

이 TData란 구조체는 8바이트에 다음과 같이 저장되어 있다.

cData
??
sData
sData
iData
iData
iData
iData

cData를 저장하고, 4바이트중에 3바이트가 남아있기 때문에 sData를 3바이트 중에 2바이트의 공간에 저장하고,
iData를 저장하려 하니 1바이트밖에 남아있지 않기 때문에 4바이트의 공간을 따로 만들어 저장하게 되는 것이다.

그럼 이제 위의 소스에서 변수 선언의 순서를 한 번 바꿔 보자.



변수 선언의 순서를 바꿨을 뿐인데 신기하게도 같은 구조체의 크기가 8에서 12로 늘어나버렸다.
이 TData 구조체 변수는 다음과 같이 저장되어 있을 것이다.

cData(1byte)
empty(3byte)
sData(2byte)
empty(2byte)
iData(4byte)

이처럼 컴파일러는 4바이트에 맞춰서 데이터를 저장하는 것을 볼 수 있다. 이것을 막으려면 어떻게 해야할까.

이것을 해결하려면 #pragma pack() 이라는 전처리어를 사용하면 된다.
구조체 하나를 더 추가한 다음 소스를 보자.



#pragma pack(1)에서 1은 1바이트 단위로 저장하겠다는 것이다. 따라서 TData와 TData2의 내용물은 같으나 크기는 다른 것을 확인할 수 있다.

 그렇다면, 왜 모두 1바이트로 해서 메모리의 낭비가 없도록 하지 않는 것일까?
 그것은, 아까도 이야기하였듯이 32비트 CPU에서는 4바이트(32비트)의 단위로 데이터를 처리하는 것이 가장 빠르게 때문이다. 즉, #pragma pack(1) 이라고 선언해놓고 원래대로 돌려놓지 않는다면 속도저하의 문제가 생길 수 있다.
 따라서, 위의 소스에서 구조체 선언이 끝나는 부분에 #pragma pack(4)라고 선언해주어 할 것이다.

 하지만, 여기에도 문제가 있다.
 만약, 이 소스를 32비트의 PC가 아닌 다른 CPU가 장착된 장비에서 컴파일하게 된다면 어떻게 될 것인가. 예를 들면 임베디드 시스템 같은 8~16비트 CPU에서 말이다.
 소스를 일일히 찾아서 CPU에 맞게 고쳐주고 다시 컴파일해야 되는 불편함과 어려움이 생기게 된다.

 이럴때를 위해서 좀 더 우아하게 쓰는 코드가 있다.



 기존의 바이트를 스택에 push하고 1바이트 단위로 처리한다음 끝나는 부분에 원래의 바이트 단위를 pop해주는 코드이다. 보통은 이렇게 사용하면 되겠다.

 pragma에는 다른 용도(warning을 표시하지 않게 한다던가하는 기능들)도 있지만, 이는 다음에 알아보도록 하자.
2009. 4. 28. 11:50

[ATmega128] RS232 시리얼 통신 관련 레지스터 정리

USARTn I/O Data Register - UDRn

 RXBn[7:0]
 TXBn[7:0]

n은 ATmega128에는 두개의 레지스터가 있으므로, 0과 1로 두개를 구별한다.(ex. UDR0, UDR1)
데이터를 쓸 때는 TXBn에, 읽을 때는 RXBn에서 읽는다.


USART Control and Status Register A - UCSRnA

 RXCn TXCn
UDREn
FEn
DORn
UPEn
U2Xn
MPCMn

7bit - 2bit : Status      1bit - 0bit : Control
RXCn : USART Receive Complete
 - 이 레지스터는 수신버퍼(UDRn)에 읽지 않은 데이터가 있으면 1, 수신버퍼가 비어있으면 0이 된다.
TXCn : USART Transmit Complete
 - 송신 시프트 레지스터에 있는 송신 데이터가 모두 송신되고 UDRn의 송신 버퍼에 아직 새로운 데이터가 Write되지 않은 상태이면 1이 된다. 즉, 시프트 레지스터와 송신 버퍼 둘다 비었을 때. 송신 완료 인터럽트를 발생시킬 때 사용.
UDREn : USART Data Register Empty
 - 송신버퍼에 새로운 데이터를 받을 준비가 되어 있으면 1로 set. 즉. 송신 버퍼가 비었을 때.
FEn : Frame Error
 - 수신 버퍼에서 데이터를 수신하는 동안 프레임 에러가 발생하면 1로 set.
DORn : Data OverRun
 - 수신 동작에서 오버런 에러가 발생하였음을 나타내는 상태 플래그.
 오버런 에러 : 수신 버퍼에 현재 읽지 않은 수신 문자가 들어와있는 상태에서 수신 시프트 레지스터에 새로운 데이터 문자가 수신 완료되고 다시 그 다음 수신 데이터인 3번째 문자의 스타트 비트가 검출되면 발생.(즉, 시프트 레지스터에서 데이터가 완전히 전송되지 않았는데 새로운 데이터가 덮어버릴 때)
UPEn : Parity Error
 - 수신 버퍼로 부터 데이터를 수신하는 동안 패리티 에러가 발생하였음을 알리는 플래그.
U2Xn : Double the USART Transmission Speed
 - 비동기 모드에서만 사용 가능하며, 클럭의 n분주비를 16에서 8로 1/2만큼 낮추어 전송속도를 2배 높이는 기능을 한다.
MPCMn : Multi-Processor Communication Mode
 - USARTn을 멀티 프로세서 통신모드로 설정하고, 멀티 프로세서 통신 어드레스 정보를 포함하지 않는 모든 수신 데이터는 수신부에 의해 무시된다.


USARTn Control and Status Register B - UCSRnB

 RXCIEn TXCIE
UDRIEn
RXENn
TXENn
UCSZn2
RXB8n
TXB8n

RXCIEn : RX Complete Interrupt Enable
 - 수신 완료 인터럽트를 발생시키는 플래그
TXCIE : TX Complete Interrupt Enable
 - 송신 완료 인터럽트를 발생시키는 플래그
UDRIEn : USART Data Register Empty Interrupt Enable
 - USARTn 데이터 레지스터가 비어있다는 인터럽트를 발생시키는 플래그
RXENn : Receiver Enable
 - 수신기를 활성화한다.
TXENn : Transmitter Enable
 - 송신기를 활성화한다.
UCSZn2: Character Size
 - UCSZn1~0 비트와 함께 전송 문자의 데이터 비트수를 설정하는데 쓰인다.
RXB8n : Receive Data Bit 8
 - 수신할 때 9번째 비트를 받을지를 결정하는 플래그
TXB8n : Transmit Data Bit 8
 - 송신할 때 9번째 비트를 보낼지를 결정하는 플래그


USART Control and Status Register C - UCSRnC

-
 UMSELn UPMn1
UPMn0
USBSn
UCSZn1
UCSZn0
UCPOLn

UMSELn : USART Mode Select
 - 0: 비동기 모드
 - 1: 동기 모드
UPMn1, UPMn0 : Parity Mode
 - 00: 패리티모드를 사용하지 않음
 - 01: 사용하지 않는 플래그
 - 10 : 짝수 패리티
 - 11 : 홀수 패리티
USBSn : Stop Bit Select
 - 정지 비트 설정
 - 0 : 1bit,    1: 2bit
UCSZn1, UCSZn0 : Character Size
 - UCSZn2와 함께 전송 문자의 데이터 비트수를 설정
 UCSZn2 UCSZn1
UCSZn0
Chacter Size
0
0
0
5-bit
0 0
1
6-bit
0
1
0
7-bit
0
1
1
8-bit
1
0
0
사용안함
1
0
1
사용안함
1
1
0
사용안함
1
1
1
9-bit
UCPOLn : Clock Polarity
 - 동기모드에서만 사용하는 플래그로 0이면 송신 데이터는 XCKn 클럭의 상승 에지에 새로운 값이 출력되고, 하강 에지에 수신데이터가 검출된다. 1이면 반대로 출력과 검출이 된다.


USART Baud Rate Registers - UBRRnL and UBRRnH

UBRRnH
7 - 4 bit reserved(사용안함)
UBRRn[11:8]
UBRRnL
                                                                    UBRRn[7:0]

 - UBRRn11 - 0 : USART Baud Rate Register
12비트를 이용하여 USARTn의 보울(Baud rate)을 결정하는데, UBRRnH 3비트와 UBRRnL의 8비트가 조합을 하여 사용한다.