2012년 9월 4일 화요일
리버싱 때 유용한 API
파일 생성하거나 열기(Open) : 파일을 생성하거나 열 때
32bit : CreateFileA
wide : CreateFileW
파일 입출력(Read&Write)
ReadFile : 읽기
WriteFile : 쓰기
파일 접근(Access)
SetFilePointer
시스템 디렉터리를 얻어오는 함수
32bit : GetSystemDirectoryA
wide : GetSystemDirectoryW
.ini 파일과 관련된 함수 : ini 구성 설정에 관련된 함수들
32bit : GetPrivateProfileStringA, GetPrivateProfileIntA,
WritePrivateProfileStringA, WritePrivateProfileIntAwide : GetPrivateProfileStringW, GetPrivateProfileIntW,
WritePrivateProfileStringW, WritePrivateProfileIntW
레지스트리와 관련된 함수 : 레지스트리의 키를 생성 혹은 삭제할 때
32bit : RegCreateKeyA, RegDeleteKeyA
wide : RegCreateKeyW, RegDeleteKeyW
현재 열린 레지스트리 키를 읽을 때
32bit : RegQueryValueA
wide : RegQueryValueW
레지스트리 키를 열 때
32bit : RegCloseKeyA, RegOpenKeyA
wide : RegCloseKeyW, RegOpenKeyW
객체에서 문자열을 읽을 때
32bit : GetWindowTextA, GetDlgItemTextA
wide : GetWindowTextW, GetDlgItemTextW
객체의 텍스트를 지정
32bit : SetWindowTextA, SetDlgItemTextA
wide : SetWindowTextW, SetDlgItemTextW
메시지 박스
32bit : MessageBoxA, MessageBoxExA
wide : MessageBoxW, MessageBoxExW
메시지 관련
32bit : SendMessageA
wide : SendMessageW
날짜와 시간 : 날짜와 시간을 구할 때
GetLocalTime
SystemTimeToFileTime
윈도우를 생성하거나 제거할 때
32bit : CreateWindowA, CreateWindowExA, DialogBoxParamA
wide : CreateWindowW, CreateWindowExW, DialogBoxParamW
참고 : 리버스엔지니어링 : 역분석 구조와 원리 박병익/이강석 공저
2012년 9월 3일 월요일
윈도우즈 시스템 프로그래밍 [8] - 프로세스간 통신 (IPC) 2.
윈도우즈 시스템 프로그래밍을 공부하면서 정리한 글입니다.
7장. 프로세스간 통신 (IPC) 1
I. 프로세스간 통신(IPC)의 의미
1. 프로세스의 커널 오브젝트 핸들 테이블
- 마이크로소프트에서 Windows 운영체제 소스코드를 공개하지 않아 핸들 테이블이 어떻게 관리되는지
알 방법이 없음
- 리소스 생성 과정에서 커널 오브젝트가 생성되면, 해당 오브젝트의 핸들이 반환
① 핸들과 커널 오브젝트의 리뷰 (Review)
=> 핸들 256을 통해 0x2400번지에 할당 되어 있는 메일 슬롯에 접근 가능
BUT 핸들 256이 0x2400번지의 커널 오브젝트를 의미한다는 정보가 없음
=> ∴핸들 테이블 개념을 도입
② 핸들 테이블의 도입
- 핸들 테이블은 핸들 정보를 저장하고 있는 테이블로서 프로세스 별로 독립적
2. 핸들의 상속
- CreateProcess 함수 호출 시 다섯번째 인자가 무엇이냐에 따라 부모 프로세스의 핸들 테이블이
자식 프로세스에게 상속될 수 있음
① 핸들의 상속에 대한 이해
- 자식 프로세스는 부모 프로세스의 핸들 테이블 정보를 상속 받을 수 있음
but 모든 핸들 정보를 상속 받진 않음
- 핸들 테이블에는 해당 핸들의 상속 여부 컬럼(column)이 존재
- 상속시, 상속 여부에 대한 정보도 변경 없이 그대로 상속됨
=> 자식 프로세스에 상속된 핸들은 또 다른 자식 프로세스 만들때도 상속됨
- 상속되는 핸들을 결정 하는건 프로그래머 몫
② 핸들의 상속을 위한 전달 인자
BOOL CreateProcess (
LPCTSTR lpApplicationName,
LPTSTR lpCommandLine,
...
BOOL bInheritHandles // 핸들 상속 인자
...
);
- bInheritHandles -> TRUE 전달할 경우 부모 프로세스의 핸들 테이블은 상속됨
-> BUT 상속 여부가 Y 로 설정된 핸들에 한해서만 상속이 이루어짐
③ 핸들의 상속과 커널 오브젝트의 Usage Count (UC)
- 프로세스가 핸들을 얻게 되었다는 의미 : 핸들 테이블에 대한 정보가 갱신됨
ex) CreateMailslot 호출
⑴ 메일 슬롯 리소스 생성
⑵ 커널 오브젝트 생성
⑶ 핸들 정보가 핸들 테이블에 갱신 // 이때부터 프로세스가 핸들 정보를 얻음
⑷ CreateMailslot 함수를 빠져 나오면서 핸들값 반환
- 부모 프로세스 핸들 테이블에 있는 커널 오브젝트들 UC가 1
- 자식 프로세스 생성 후 상속여부가 Y 였던 커널 오브젝트는 UC가 1 증가 => UC=2가 됨
④ 상속이 되기 위한 핸들의 조건
- 핸들의 상속 여부는 리소스 생성되는 순간 결정 (핸들 테이블의 Y or N)
- LPSECURITY_ATTRIBUTES 구조체 = SECURITY_ATTRIBUTES의 포인터로 정의
- NULL 이 전달되면 상속이 되지 않음
BUT 구조체 변수를 적절히 초기화해 주소값 전달하면 상속 가능
typedef struct _SECURITY_ATTRIBUTES {
DWORD nLength; // 구조체 변수 크기
LPVOID lpSecurityDescriptor; // 상속과 관계 없음
BOOL bInheritHandle; // 상속 여부 결정 TRUE : Y, FALSE : N
}
ex)
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
HANDLE hMailSlot = CreateMailslot( ... , &sa);
CreateProcess( ... , &sa, ... );
- 프로세스의 핸들은 Pseudo (가짜) 핸들
- 리소스 생성 과정에서 커널 오브젝트가 생성되면, 해당 오브젝트의 핸들이 반환
① 핸들과 커널 오브젝트의 리뷰 (Review)
=> 핸들 256을 통해 0x2400번지에 할당 되어 있는 메일 슬롯에 접근 가능
BUT 핸들 256이 0x2400번지의 커널 오브젝트를 의미한다는 정보가 없음
=> ∴핸들 테이블 개념을 도입
② 핸들 테이블의 도입
- 핸들 테이블은 핸들 정보를 저장하고 있는 테이블로서 프로세스 별로 독립적
2. 핸들의 상속
- CreateProcess 함수 호출 시 다섯번째 인자가 무엇이냐에 따라 부모 프로세스의 핸들 테이블이
자식 프로세스에게 상속될 수 있음
① 핸들의 상속에 대한 이해
- 자식 프로세스는 부모 프로세스의 핸들 테이블 정보를 상속 받을 수 있음
but 모든 핸들 정보를 상속 받진 않음
- 핸들 테이블에는 해당 핸들의 상속 여부 컬럼(column)이 존재
- 상속시, 상속 여부에 대한 정보도 변경 없이 그대로 상속됨
=> 자식 프로세스에 상속된 핸들은 또 다른 자식 프로세스 만들때도 상속됨
- 상속되는 핸들을 결정 하는건 프로그래머 몫
② 핸들의 상속을 위한 전달 인자
BOOL CreateProcess (
LPCTSTR lpApplicationName,
LPTSTR lpCommandLine,
...
BOOL bInheritHandles // 핸들 상속 인자
...
);
- bInheritHandles -> TRUE 전달할 경우 부모 프로세스의 핸들 테이블은 상속됨
-> BUT 상속 여부가 Y 로 설정된 핸들에 한해서만 상속이 이루어짐
③ 핸들의 상속과 커널 오브젝트의 Usage Count (UC)
- 프로세스가 핸들을 얻게 되었다는 의미 : 핸들 테이블에 대한 정보가 갱신됨
ex) CreateMailslot 호출
⑴ 메일 슬롯 리소스 생성
⑵ 커널 오브젝트 생성
⑶ 핸들 정보가 핸들 테이블에 갱신 // 이때부터 프로세스가 핸들 정보를 얻음
⑷ CreateMailslot 함수를 빠져 나오면서 핸들값 반환
- 부모 프로세스 핸들 테이블에 있는 커널 오브젝트들 UC가 1
- 자식 프로세스 생성 후 상속여부가 Y 였던 커널 오브젝트는 UC가 1 증가 => UC=2가 됨
④ 상속이 되기 위한 핸들의 조건
- 핸들의 상속 여부는 리소스 생성되는 순간 결정 (핸들 테이블의 Y or N)
- LPSECURITY_ATTRIBUTES 구조체 = SECURITY_ATTRIBUTES의 포인터로 정의
- NULL 이 전달되면 상속이 되지 않음
BUT 구조체 변수를 적절히 초기화해 주소값 전달하면 상속 가능
typedef struct _SECURITY_ATTRIBUTES {
DWORD nLength; // 구조체 변수 크기
LPVOID lpSecurityDescriptor; // 상속과 관계 없음
BOOL bInheritHandle; // 상속 여부 결정 TRUE : Y, FALSE : N
}
ex)
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
HANDLE hMailSlot = CreateMailslot( ... , &sa);
CreateProcess( ... , &sa, ... );
II. Pseudo (가짜) 핸들과 핸들의 중복 (Duplicate)
1. 프로세스의 커널 오브젝트 핸들 테이블
2012년 8월 29일 수요일
윈도우즈 시스템 프로그래밍 [7] - 프로세스간 통신 (IPC)
윈도우즈 시스템 프로그래밍을 공부하면서 정리한 글입니다.
7장. 프로세스간 통신 (IPC) 1
I. 프로세스간 통신(IPC)의 의미
- IPC = Inter-Process Communication ( 프로세스 사이의 통신 )
- 통신 : 기본적으로 데이터를 주고 받는 행위
- 대부분 하나의 프로그램 = 하나의 프로세스 but 여러개의 프로세스를 가진 프로그램도 종종 있음
1. 프로세스 사이에서 통신이 이뤄지기 위한 조건
- 서로 통신하고자 하는 프로세스가 만날 수 있는 여건 (공유하는 메모리 영역이 존재)이 되어야 함
- 여건이 허락되지 않으면 전화 or 메신저 같은 보조 수단이 필요
2. 프로세스들이 서로 만날 수 없는 이유
- 프로세스는 서로 데이터를 주고 받을 만한 접선 장소가 없기 때문에 데이터 주고받는 것은 불가능
- 프로세스는 자신에게 할당된 메모리 공간 이외의 접근은 불가능
3. 프로세스들이 만나지 못하게 디자인한 이유
- 안정성을 높이기 위해
II. 메일 슬롯 방식의 IPC
1. 메일 슬롯 (Mail Slot)의 원리
- 파이프와 더불어 대표적인 IPC 기법
- 메일 슬롯 : 편지를 넣을 가느다란 우체통의 입구
- 기본 원리 : 데이터를 주고받기 위해 프로세스가 우체통을 마련
2. 메일 슬롯 (Mail Slot) 구성을 위해 필요한 요소
① Receiver가 준비해야할 것
HANDLE CreateMailslot (
LPCTSTR lpName,
DWORD nMAXMessageSize,
DWORD lReadTimeout,
LPSECUritY_ATTRIBUTES lpSecurityAttributes
);
1) lpName : 생성하는 메일 슬롯의 이름
- 주소의 기본 형식 : \\computer name\mailslot\[path] name
2) nMaxMessageSize : 메일 슬롯의 버퍼 크기를 지정
- 0을 전달할 경우 시스템이 허용하는 최대 크기로 지정
3) lReadTimeout : 최대 블로킹 시간을 ms 단위로 전달
- 메일 슬롯은 전소오딘 데이터를 읽기위해 파일 입/출력 함수 ReadFile 함수를 이용
- 메일 슬롯이 비어 있다면 데이터가 채워질 때 까지 Blocking 상태에 놓임
- 0을 전달하면 데이터가 있든 없든 블로킹 상태를 빠져나와 다음 단계를 실행
- MAILSLOT_WAIT_FOREVER을 인자로 전달하면 데이터가 존재할 때 까지 블로킹 상태에 놓임
4) lpSecurrityAttributes : 핸들의 상속 여부를 지정
5) 함수의 변환 타입 : HANDLE
=> 메일 슬롯은 커널에 의해 관리되는 리소스 이므로 커널 오브젝트가 생성되며, 그 핸들이 반환됨
② Sender가 준비해야 할 것
- 메일 슬롯 이름을 알아야 함 (&주소 ( 디렉토리))
ex)
HANDLE hMailSlot;
hMailSlot=CreateFile("\\\\.\\mailslot\\ ", ...); // 파일을 생성하거나 개방할때 사용
CHAR message[50];
WriteFile(hMailSlot, message, ...);
- CreateFile, WriteFile : Windows에서 제공하는 파일 관련 시스템 함수 ( <-> ANSI 표준함수)
HANDLE hMailSlot;
hMailSlot=CreateFile("\\\\.\\mailslot\\ ", ...); // 파일을 생성하거나 개방할때 사용
CHAR message[50];
WriteFile(hMailSlot, message, ...);
- CreateFile, WriteFile : Windows에서 제공하는 파일 관련 시스템 함수 ( <-> ANSI 표준함수)
- CreateFile을 통해 메일 슬롯으로 보내기 위한 통로(리소스)를 형성
=> 데이터 전송을위한 Data Stream을 형성
- 메일 슬롯은 파일이 아니지만 파일 입/출력 함수를 사용
-> Windows 파일 시스템을 기반으로 구현되어 있기 때문
- 주소 골격 : \\computername\mailslot\[path] name
- 주소에서 'mailslot' 부분은 바뀌지 않음
- computername에 '.'의 의미 : 로컬 컴퓨터
=> 데이터 전송을위한 Data Stream을 형성
- 메일 슬롯은 파일이 아니지만 파일 입/출력 함수를 사용
-> Windows 파일 시스템을 기반으로 구현되어 있기 때문
- 주소 골격 : \\computername\mailslot\[path] name
- 주소에서 'mailslot' 부분은 바뀌지 않음
- computername에 '.'의 의미 : 로컬 컴퓨터
- Sender 프로세스는 우체통(메일 슬롯)의 주소를 알고 있어야 데이터 전송이 가능
- 메일 슬롯 = 메모리 공유 역할
- 데이터 전송이 양방향이 아닌 단방향(우체통)
- 여러개의 리시버가 같은 주소를 가질 수 있음
=> 센더가 그 주소로 전송하면 모든 메일 슬롯에 데이터 전송이 가능 (브로드 케스팅)
- CreateFile : 파일 만들거나 오픈
- WriteFile : 파일에 데이터 작성
- IPC 메일 슬롯을 이용하지 않고 파일만으로 데이터를 주고받을 수 있음
ex)
프로세스 A가 파일 a.txt를 만들고 프로세스 B가 a.txt를 읽음
=> BUT 안정성에 문제가 있음 -> ∴ 메일 슬롯을 이용
ex) Receiver.cpp
#define SLOT_NAME _T("\\\\.\\mailslot\\mailbox");
int _tmain(int argc, LPTSTR argv[]) {
HANDLE hMailSlot;
TCHAR messageBox[50];
DWORD bytesRead;
hMailSlot = CreateMailSlot (
SLOT_NAME,
0,
MAILSLOT_WAIT_FOREVER,
NULL
);
ReadFile(hMailSlot, messageBox, sizeof(TCHAR)*50, &bytesRead, NULL);
closeHandle(hMailSlot); // UC 1줄여 커널 오브젝트의 소멸을 도움
ex) Sender.cpp
#define SLOT_NAME _T("\\\\.\\mailslot\\mailbox");
int _tmain(int argc, LPTSRT argv[]) {
HANDLE hMailSlot;
TCHAR message[50];
DWORD bytesWritten;
hMailSlot=CreateFile ( // Receiver과 연결을 만들기 위해
SLOT_NAME,
GENERIC_WRITE, // 여기서부터
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL // 여기까지 거의 고정
);
_fgetts(message, sizeof(messsage)/sizeof(TCHAR), stdin);
WriteFile(hMail, message, _tcslen(message)*sizeof(TCHAR), &bytesWritten, NULL);
CloseHandle(hMailSlot);
- 메일 슬롯 주소에서 '.'는 컴퓨터 자신, '*'는 모든 컴퓨터
=> 브로드 케스팅 이용 가능
III. Signaled vs Non-Signaled => 커널 오브젝트의 상태
1. 상태에 대한 이해
- 상태(state)라는 용어를 사용하는 이유는 변하기 때문
- signaled (신호를 받는 상태)와 Non-Signaled (신호를 받지 않는 상태)
- 커널 오브젝트를 구성하는 멤버 변수 중 하나가 상태 정보를 저장함
=> TRUE : Signaled FALSE : Non-Signaled
2. 프로세스 커널 오브젝트 상태 (state)에 대한 이해
- 프로세스가 생성될 때 프로세스 커널 오브젝트가 생성됨
=> 처음 상태 : Non-signaled
- 프로세스가 종료되면 Signaled 상태로 변경됨
- Signaled 된 커널 오브젝트는 Non-Signaled 상태로 변경되지 않음
=> 종료된 프로세스를 다시 재개하지 못하기 때문 ( 새로운 프로세스를 다시 실행해야 함)
3. 커널 오브젝트의 두가지 상태를 확인하는 용도의 함수
- 커널 오브젝트의 상태를 확인하는 대표적인 함수
ex) DWORD WaitForSingleObject (
HANDLE hHandle, // 확인하려는 커널 오브젝트의 핸들
DWORD dwMilliseconds // 커널 오브젝트가 Signaled 될 때 까지 기다릴 수 있는 최대 시간
);
- dwMilliseconds에 IFINITE 인자 전달시 Signaled 될 때까지 기다림
- WaitForSingleObject 반환값
1) WAIT_OBJECT_0 : 커널 오브젝트가 Signaled 되었을 때 반환값
2) WAIT_TIMEOUT : Signaled 되지 않고 dwMillisecons가 다 되었을 때
3) WAIT_ABANDONED : 오류 발생시
- 이런 상황에서 WaitForSingleObject 사용
참고 : 뇌를 자극하는 윈도우즈 시스템 프로그래밍. 윤성우 저
2012년 8월 27일 월요일
윈도우즈 시스템 프로그래밍 [6] - 커널 오브젝트와 오브젝트 핸들
윈도우즈 시스템 프로그래밍을 공부하면서 정리한 글입니다.
6장. 커널 오브젝트와 오브젝트 핸들
I. 커널 오브젝트에 대한 이해
- 커널 : 컴퓨터를 운영하는데 있어서 중심이 되는 운영체제의 핵심 부분
-> 일반적으로 커널과 운영체제를 같은 의미로 사용
- 커널 오브젝트 : 커널에서 관리하는 중요한 정보들을 담아둔 데이터 블록
1. 커널 오브젝트의 이해
- 프로세스의 생성과 소멸, 상태 벼화, 관리는 모두 운영체제 몫
- 여러개의 프로세스르 관리하면 고정적으로 저장하고 갱신할 정보가 존재
ex) 프로세스 상태 정보 ( Running, Blocked, Ready 상태 ), 우선순위 정보
=> 운영체제 내부에 저장되어야 할 정보, 변경될 때 마다 갱신되어야 함
-> 프로세스 스케쥴러가 이를 바탕으로 프로세스를 관리
- 프로세스에 관련된 정보를 저장, 참조 및 변경할 수 있는 구조체 존재
(편의상 '프로세스 관리 구조체'라 함)
- 프로세스가 생성된 후 정보를 초기화 한 것이 커널 오브젝트
- 프로세스 관리 구조체는 어떻게 구성되어 있는지 알 수 없음 (마이크로소프트가 공개하지 않음)
=> 프로세스의 커널 오브젝트는 프로그래머가 직접 생성하거나 조작하는 것을 막음
- Windows 커널에 의해 관리되는 리소스 수 만큼 커널 오브젝트도 생성됨
CreateProcess 함수 호출이 커널 오브젝트의 생성 원인이나 단지 프로세스의 생성을 요청할 뿐
프로세스 생성의 주체는 운영체제이다. (함수가 아님)
- 프로세스 생성시, 그 프로세스르 위한 커널 오브젝트가 생성됨
- but 프로세스가 소멸한다고 해서 커널 오브젝트가 소멸하진 않음
=> 소멸할 수도 있으나 하지 않을 수도 있음
- BOOL CloseHandle (
Handle hObject // 핸들을 닫음(반환) -> 핸들의 소멸, 리소스 해제를 의미하지 않음
);
- 핸들값은 커널 오브젝트에 매핑되지 않음 -> 각각의 핸들 테이블에서 핸들 값을 사용
=> ∴ 핸들 값, 핸들 테이블은 프로세스에 종속적
- 자식프로세스가 사라지면 프로세스에 종속적인 핸들 테이블이 사라지나
부모 프로세스에서 자식 프로세스 커널 오브젝트를 보고 있으므로 (핸들 테이블에서 사용중)
커널 오브젝트는 사라지지 않고 단지 Usage Count를 2에서 1로 감소시킴
=> UC(Usage Count)가 존재하는 이유
- 운영체제 영역에서 커널 오브젝트가 있음 = 커널 오브젝트의 소멸은 운영체제게 UC를 통해 정함
- UC가 0이 되면 커널 오브젝트는 소멸 ( but 파일은 물리적으로 남아있음)
Q1. 프로세스 생성시 자기 자신의 커널 오브젝트를 핸들 테이블에서 참조하는가?
- 프로세스 생성시 자기 자신의 커널 오브젝트에 대한 핸들정보는 핸들 테이블에 저장하지 않음
but GetCurrentProcess는 상수(-1)을 리턴
- 함수들은 -1이 리턴 되면 현재 프로세스를 지칭 한다고 인식함 (windows의 약속)
Q2. 자식프로세스가 종료된 후 부모 프로세스가 자식 프로세스의 커널 오브젝트를 참조한다고 해서
커널 오브젝트를 남겨둘 이유가 있는가?
- 부모 프로세스는 자식 프로세스가 정상 종료 햇는가가 궁금함
- 자식 프로세스가 소멸되자 마자 자식 프로세스의 커널 오브젝트가 소멸되면
부모 프로세스는 자식 프로세스의 정상 종료 여부를 알지 못함
=> ∴ UC를 통해 운영체제가 커널 오브젝트를 관리!
- STARTUPINFO si = {0, }; // 프로세스 생성시 초기 정보를 전달하는 목적
si.cb= sizeof(si);
- PROCESS_INFORMATION pi; /// 생성된 프로세스의 정보를 얻기 위한 구조체 (핸들값 얻을 수있음)
- 자식 프로세스가 종료되더라도 부모 프로세스의 참조에 의해 자식 프로세스의 커널 오브젝트는 남음
-> but 종료코드를 얻고 더이상 커널 오브젝트를 참조할 필요가 없다면 UC를 0으로 만들어 운영체제가 소멸시키도록 해야함
( 자원 낭비를 막기위해 )
=> CloseHandle (pi.hProcess); // 함수를 이용해 UC를 하나 줄여 메모리 낭비 방지
- 프로세스 관리 구조체는 어떻게 구성되어 있는지 알 수 없음 (마이크로소프트가 공개하지 않음)
=> 프로세스의 커널 오브젝트는 프로그래머가 직접 생성하거나 조작하는 것을 막음
2. 그 이외의 커널 오브젝트
- 프로세스 내에 쓰레드를 생성할 때, IPC를 위해 사용하는 파이프나 메일 슬롯을 생성할 때도
커널 오브젝트를 생성해야 함
- 커널 오브젝트 종류에 따라 다른 구조체를 생성
ex) 파일 커널 오브젝트 -> 파일 속성 정보 (Read모드, Read / Write모드 ... )
프로세스 커널 오브젝트 -> 프로세스 우선 순위 정보 (스케쥴링을 위해)
=> ∴ Windows 운영체제는 프로세스, 쓰레드와 파일 같은 Resource 들을 원활히 관리하기 위해
데이터를 저장하는 메모리 블록이 존재 => 커널 오브젝트
- 데이트 블록 : 커널 오브젝트 | 프로세스나 파일 : Resource
- Windows 커널에 의해 관리되는 리소스 수 만큼 커널 오브젝트도 생성됨
II. 오브젝트 핸들(Handle)을 이용한 커널 오브젝트의 조작
- 프로그래머는 직접 커널 오브젝트를 조작할 수 없음
- 리소스의 특성을 변경하기 위해선 (프로세스 우선 순위 변경 같은)
해당 리소스 커널 오브젝트를 조작해야함
-> 마이크로소프트에서 제공하는 시스템 함수를 이용 (안정적인 방식)
1. 프로세스 우선순위 (Priority) 변경
ex)
BOOL SetPriorityClass (
HANDLE hProcess; // 우선 순위를 변경할 프로세스의 핸들
DWORD dwPriorityClass; // 새롭게 적용할 우선 순위
);
- 핸들 (Handle) : 커널 오브젝트에 할당되는 숫자 (정수형)
2. 커널 오브젝트에 할당되는 숫자 = 핸들 (Handle)
- Windows는 커널 오브젝트를 생성할 때 마다 핸들을 하나씩 부여
3. 핸들 정보
- 핸들 정보를 얻는 방법은 커널 오브젝트 종류에 따라서 다양함 ( 프로세스, 파일, 쓰레드 ...)
- GetCurrentProces 함수 -> 현재 실행되고 있는 프로세스의 핸들을 얻을 떄 사용
=> 항상 같은 값을 리턴(-1)
- '-1'은 자기 자신을 의미
- 함수가 호출되어 실행되는 중에 (함수는 여러개의 명령어로 구성) CPU 할당시간을
다른 프로세스에 넘겨줄 수 있음
=> 우선순위를 넘기는 기준은 CPU 관점에서 명령어 단위가 기준
III. 커널 오브젝트와 핸들의 종속관계
1. 커널 오브젝트의 종속 관계
- 커널 오브젝트는 Windows 운영체제에 종속적
① 커널 오브젝트에 종속적인 것이 아니라, 운영체제에 종속적인 관계로
커널 오브젝트의 소멸 시점은 운영체제에 의해 결정됨
② 커널 오브젝트는 여러 프로세스에 의해 접근이 가능 (물론 함수 호출을 통한 간접 호출)
2. 핸들의 종속 관계
- 핸들 (핸들 테이블) 은 운영체제에 종속적이지 않고 프로세스에 종속적
ex) 프로세스의 우선 순위를 높이기 위해서는 핸들을 이용해 커널 오브젝트에 접근.
- PROCESS_INFORMATION 구조체
typedef struct _PROCESS_INFORMATION {
HANDLE hProcess; // 프로세스 핸들
HANDLE hThread; // 쓰레드 핸들
DWORD dwProcessID; // 프로세스의 ID
DWORD dwThreadID; // 쓰레드의 ID
} PROCESS_INFORMATION;
ex) 프로세스의 우선 순위를 높이기 위해서는 핸들을 이용해 커널 오브젝트에 접근.
- PROCESS_INFORMATION 구조체
typedef struct _PROCESS_INFORMATION {
HANDLE hProcess; // 프로세스 핸들
HANDLE hThread; // 쓰레드 핸들
DWORD dwProcessID; // 프로세스의 ID
DWORD dwThreadID; // 쓰레드의 ID
} PROCESS_INFORMATION;
3. 프로세스 핸들과 ID
- 운영체제는 프로세스 생성시 프로세스들을 구분짓기 위해 ID(식별자)를 할당
- 프로세스 핸들 : 프로세스의 커널 오브젝트를 가리키기(구분짓기) 위함
- 프로세스 ID : 커널 오브젝트가 아니라 프로세스 자체를 구분짓기 위함
- CreateProcess 함수를 통해 프로세스를 생성하면 쓰레드라는 시스템 리소스도 생성됨
IV. 커널 오브젝트와 Usage Unit
1. CloseHandle 함수에대한 정확한 이해
CreateProcess 함수 호출이 커널 오브젝트의 생성 원인이나 단지 프로세스의 생성을 요청할 뿐
프로세스 생성의 주체는 운영체제이다. (함수가 아님)
- 프로세스 생성시, 그 프로세스르 위한 커널 오브젝트가 생성됨
- but 프로세스가 소멸한다고 해서 커널 오브젝트가 소멸하진 않음
=> 소멸할 수도 있으나 하지 않을 수도 있음
- BOOL CloseHandle (
Handle hObject // 핸들을 닫음(반환) -> 핸들의 소멸, 리소스 해제를 의미하지 않음
);
2. 커널 오브젝트와 Usage Count
- 자식 프로세스의 종료코드는 자식 프로세스 커널 오브젝트에 저장됨
- if 자식 프로세스가 종료될 때 커널 오브젝트도 소멸되면 종료코드는 사라짐
=> ∴ 프로세스가 종료되어도 커널 오브젝트는 동시에 소멸하지 않음
- 해당 커널 오브젝트를 참조하는 대상이 없을 때 소멸하는 것이 이상적
=> windows가 커널 오브젝트를 소멸 시키는 방법
- 프로세스의 생성과 동시에 커널 오브젝트의 Usage Count=1
- 프로세스에 접근 가능한 핸들 수가 늘어날때 마다 Usage Count 증가
- 자식프로세스의 Usage Count=2 (부모 프로세스 참조 + 자기자신 참조)
=> 부모프로세스가 CreateProcess 함수 호출과정에서 PROCESS_INFORMATION
구조체를 통해 자식 프로세스의 핸들을 얻기 때문
- 핸들값은 커널 오브젝트에 매핑되지 않음 -> 각각의 핸들 테이블에서 핸들 값을 사용
=> ∴ 핸들 값, 핸들 테이블은 프로세스에 종속적
- 자식프로세스가 사라지면 프로세스에 종속적인 핸들 테이블이 사라지나
부모 프로세스에서 자식 프로세스 커널 오브젝트를 보고 있으므로 (핸들 테이블에서 사용중)
커널 오브젝트는 사라지지 않고 단지 Usage Count를 2에서 1로 감소시킴
=> UC(Usage Count)가 존재하는 이유
- 운영체제 영역에서 커널 오브젝트가 있음 = 커널 오브젝트의 소멸은 운영체제게 UC를 통해 정함
- UC가 0이 되면 커널 오브젝트는 소멸 ( but 파일은 물리적으로 남아있음)
Q1. 프로세스 생성시 자기 자신의 커널 오브젝트를 핸들 테이블에서 참조하는가?
- 프로세스 생성시 자기 자신의 커널 오브젝트에 대한 핸들정보는 핸들 테이블에 저장하지 않음
but GetCurrentProcess는 상수(-1)을 리턴
- 함수들은 -1이 리턴 되면 현재 프로세스를 지칭 한다고 인식함 (windows의 약속)
Q2. 자식프로세스가 종료된 후 부모 프로세스가 자식 프로세스의 커널 오브젝트를 참조한다고 해서
커널 오브젝트를 남겨둘 이유가 있는가?
- 부모 프로세스는 자식 프로세스가 정상 종료 햇는가가 궁금함
- 자식 프로세스가 소멸되자 마자 자식 프로세스의 커널 오브젝트가 소멸되면
부모 프로세스는 자식 프로세스의 정상 종료 여부를 알지 못함
=> ∴ UC를 통해 운영체제가 커널 오브젝트를 관리!
- STARTUPINFO si = {0, }; // 프로세스 생성시 초기 정보를 전달하는 목적
si.cb= sizeof(si);
- PROCESS_INFORMATION pi; /// 생성된 프로세스의 정보를 얻기 위한 구조체 (핸들값 얻을 수있음)
- 자식 프로세스가 종료되더라도 부모 프로세스의 참조에 의해 자식 프로세스의 커널 오브젝트는 남음
-> but 종료코드를 얻고 더이상 커널 오브젝트를 참조할 필요가 없다면 UC를 0으로 만들어 운영체제가 소멸시키도록 해야함
( 자원 낭비를 막기위해 )
=> CloseHandle (pi.hProcess); // 함수를 이용해 UC를 하나 줄여 메모리 낭비 방지
참고 : 뇌를 자극하는 윈도우즈 시스템 프로그래밍. 윤성우 저
2012년 8월 25일 토요일
윈도우즈 시스템 프로그래밍 [5] - 프로세스의 생성과 소멸
윈도우즈 시스템 프로그래밍을 공부하면서 정리한 글입니다.
5장. 프로세스의 생성과 소멸
I. 프로세스 ( Process ) 의 이해
1. 프로세스란 무엇인가?
- 프로세스 = 실행 중에 있는 프로그램
2. 프로세스를 구성하는 요소
① execution of "C" program
- Data 영역 : 전역 변수, static 변수의 할당을 위한 공간
- Stack 영역 : 지역 변수, 함수 호출시 전달되는 인자 값의 저장을 위한 공간
- Heap 영역 : 동적 할당 (malloc, calloc) 함수를 위한 공간
- Code 영역 : 시행 파일을 구성하는 명령어들이 올라가는 메모리 영역
② register Set
- 프로그램 실행을 위해선 레지스터가 절대적으로 필요
- 레지스터의 상태도 프로세스의 일부로 포함시킬 수 있음
=> 컨텍스트 스위칭(context switching)의 이해에 중요!
II. 프로세스의 스케쥴링과 상태 변화
1. 프로세스의 스케쥴링
- 하나의 CPU가 여러개의 프로세스를 번갈아가며 실행
=> 고속으로 실행하므로 사용자는 동시라고 느낌
∴ 여러개의 프로세스가 CPU 할당 시간을 나누기 때문에 동시 실행처럼 보임
2. 스케쥴링의 기본 원리
- 스케쥴링 : 프로세스의 CPU 할당 순서 및 방법을 결정짓는 일
- 스케쥴러 : 스케쥴링 알고리즘을 적용해 프로세스를 관리하는 운영체제 요소 ( 모듈)
3. 멀티 프로세스는 CPU를 바쁘게 함
- A프로세스가 I/O에 관련된 일( ex. 네트워크 송수신 )을 할 경우, 운영체제는 스케쥴러를 통해
다른 프로세스가 실행되도록 스케쥴링 함
- I/O가 프로그램 실행에서 많은 부분을 차지
4. 프로세스의 상태변화
① S(Start)에서 Ready 상태로의 전이
- S는 프로세스가 생성되었음을 의미 => 생성과 동시에 Ready 상태가 됨
- Ready 상태 = CPU에 할당 되기를 기다리는 상태
-> 멀티 프로세스 운영체제 => 실행 중인 프로세스가 있을것
② Ready 상태에서 Running 상태로의 전이
- 스케쥴러는 Ready 상태의 프로세스 중 하나를 실행할 수 있음
③ Running 상태에서 Ready 상태로 전이
- 프로세스는 생성시 중요도에 따라 우선순위 (Priority) 가 매겨짐
=> 다시 Ready상태가 된 프로세스는 다른 프로세스(우선순위가 높은)가 종료되기를 기다려야 함
④ Running 상태에서 Blocked 상태로 전이
- 실행 중에 있는 프로세스가 멈추는 (Blocked) 상태로 들어가는 것
- 데이터 입/출력에 관련된 일을 하는 경우 발생
=> 입/출력에 시간이 많이 걸림 -> 입/출력 진행 중인 프로세스는 잠시 Blocked 상태로 돌리고
Ready 상태의 프로세스를 대신 실행시킴
⑴ 프로세스 A가 파일을 입/출력 해야하는 상황을 만남
⑵ Blocked 상태가 되어 파일 입/출력을 진행
⑶ 프로세스 A가 Blocked 되는 동시에 Ready 상태의 프로세스가 Running 됨
⑤ Blocked 상태에서 Ready 상태로의 전이
- Blocked 상태는 스케쥴러에 의해 선택될 수 없는 상태
=> Ready 상태와의 차이점. Ready 상태에선 스케쥴러에 의해 실행 가능
- 입/출력이 완료된 Blocked 프로세스는 다시 Ready 상태가 되어 스케쥴러의 선택을 기다려야 함.
- S는 프로세스가 생성되었음을 의미 => 생성과 동시에 Ready 상태가 됨
- Ready 상태 = CPU에 할당 되기를 기다리는 상태
-> 멀티 프로세스 운영체제 => 실행 중인 프로세스가 있을것
② Ready 상태에서 Running 상태로의 전이
- 스케쥴러는 Ready 상태의 프로세스 중 하나를 실행할 수 있음
③ Running 상태에서 Ready 상태로 전이
- 프로세스는 생성시 중요도에 따라 우선순위 (Priority) 가 매겨짐
=> 다시 Ready상태가 된 프로세스는 다른 프로세스(우선순위가 높은)가 종료되기를 기다려야 함
④ Running 상태에서 Blocked 상태로 전이
- 실행 중에 있는 프로세스가 멈추는 (Blocked) 상태로 들어가는 것
- 데이터 입/출력에 관련된 일을 하는 경우 발생
=> 입/출력에 시간이 많이 걸림 -> 입/출력 진행 중인 프로세스는 잠시 Blocked 상태로 돌리고
Ready 상태의 프로세스를 대신 실행시킴
⑴ 프로세스 A가 파일을 입/출력 해야하는 상황을 만남
⑵ Blocked 상태가 되어 파일 입/출력을 진행
⑶ 프로세스 A가 Blocked 되는 동시에 Ready 상태의 프로세스가 Running 됨
⑤ Blocked 상태에서 Ready 상태로의 전이
- Blocked 상태는 스케쥴러에 의해 선택될 수 없는 상태
=> Ready 상태와의 차이점. Ready 상태에선 스케쥴러에 의해 실행 가능
- 입/출력이 완료된 Blocked 프로세스는 다시 Ready 상태가 되어 스케쥴러의 선택을 기다려야 함.
5. 프로세스의 상태 변화, 시나리오 다시 이해
- Running -> Ready : 해야할 일이 있으나, 어쩔 수 없이 양보해야하는 상황
ex) 우선순위가 높은 프로세스의 등장
but 항상 스케쥴러의 관심 대상이 됨
- Running -> Blocked : 해야할 일을 하고, 다른 프로세스에 Running 상태를 양보
ex) I/O 관련 작업 -> CPU 연산을 필요로 하지 않음 => 다른 프로세스의 CPU 연산을 양보
but 스케쥴러의 관심 대상이 되지 않음 -> Blocked 작업이 끝난 후 Ready로 가서 선택을 기다림
III. 컨텍스트 스위칭 (Context Switching)
- 실행중인 프로세스의 벼녕은 시스템에 많은 부하를 줌
- CPU 내에 레지스터는 현재 실행 중인 프로세스 데이터로 채워짐
=> ∴ 프로세스가 바뀌면 레지스터 값도 백업 후 변경되어야 함.
- Running -> Ready 될 때 레지스터 값은 메모리에 저장해 두어야 함 (Back Up)
- Running 프로세스와 Ready 프로세스가 바뀔 떄 => 컨텍스트 스위칭 발생
-> 실행되는 프로세스의 변경과정에서의 컨텍스트 스위칭은 시스템에 부담을 줌
=> 레지스터가 많을수록, 프로세스별 관리 데이터가 많을 수록 더 함 -> 멀티 프로세스의 단점.
IV. 프로세스의 생성
1. 프로세스의 생성
방법 1 : 실행 파일을 더블 클릭해 독립적인 프로세스를 생성
방법 2 : 프로그램 실행 중에 또 하나의 프로세스를 생성
2. CreateProcess 함수의 이해
- Windows에서 프로세스 생성을 위해 제공되는 함수
- CreateProcess 함수를 호출하는 프로세스 -> 부모 프로세스 (Parent Process)
- 호출 당하는 프로세스 -> 자식 프로세스 (Child Process)
BOOL CreateProcess (
LPCTSTR lpApplicationName, // ①
LPTCSTR lpCommandLine, // ②
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo, // ③
LPPROCESS_INFORMATION lpProcessInformation // ④
)
① lpApplicationName : 생성할 프로세스의 실행파일 이름
- 경로명을 지정할 수 있음
- 경로를 지정하지 않으면 프로그램의 현재 디렉토리에서 찾게 됨
② lpCommandLine :
- argc, argv와 같이 새로 생성하는 프로세스에 인자를 전달할 때
- lpApplicationName이 NULL일 경우 표준 검색경로에 따라 실행할 파일을 찾음
③ lpStartupInfo :
- STARTUPINFO 구조체 변수들을 초기화 한 후, 이 변수의 포인터를 전달함
④ lpProcessInformation
- 생성하는 프로세스의 정보를 얻기 위해 사용되는 인자
- PROCESS_INFORMATION 구조체 변수의 주소값을 인자로 전달
참고 : 뇌를 자극하는 윈도우즈 시스템 프로그래밍. 윤성우 저
2012년 8월 24일 금요일
윈도우즈 시스템 프로그래밍 [4] - 컴퓨터 구조 2
윈도우즈 시스템 프로그래밍을 공부하면서 정리한 글입니다.
4장. 컴퓨터 구조 2
I. 컴퓨터 구조의 접근 방법
1. 컴퓨터를 디자인 하자
- CPU의 기본 요소 : ALU, Control Unit, Registers
- 이중 register 만을 디자인 대상으로 삼음
- 시스템 프로그래머(어셈블리 프로그래머)가 CPU를 볼 때 register에 초점을 둠
2. 레지스터를 디자인하자
① 몇 비트로 구성? 16비트
② 몇 개의 레지스터로 구성? 8개 (r0 ~ r7)
③ 레지스터 각각의 용도?
- ir : instruction register
- sp : stack pointer
- lr : link register
- pc : program counter
3. 명령어 구조를 디자인 하자
- CPU 구성 형태 ( 레지스터 구성 형태 )에 따라 명령어 구조가 달라짐
∴ 레지스터를 디자인 한 후 명령어를 디자인 함.
- 어셈블리어로 구현된 프로그램은 구조가 다른 CPU로 인식이 불가능
- 16비트를 최대한 효율적으로 활용할 수 있도록 구성
2^16 = 65536개
C / C++ 연산자 = 100개를 넘지 않음
ex) 레지스터 r1에 있는 값과 7을 더해 r2에 저장하라
=> 총 4개의 정보 필요 : "덧셈", "레지스터 r1", "레지스터 r2", "숫자 7"
- 연산자 :
덧셈 : ADD, 001
뺄셈 : SUB, 010
곱셈 : MUL, 011
나눗셈 : DIV, 100
- 저장소 : 레지스터가 8개 이므로 3비트 할당
- 피연산자 1, 2 : 숫자가 들어갈 수 있도록 디자인
- 피연산자 부분 -> 저장된 데이터가 숫자인지 레지스터인지 구분이 어려움
=> ∴ 네개의 비트 중 첫번째 비트가 1이면 레지스터 정보를 나타냄
ex) 1001 : r1, 0001 : 숫자 1
but 숫자를 8개 밖에 표현하지 못하는 제약 사항이 있음
- 명령어는 ir (instruction resiter )에 저장됨
=> 다음번 실행될 명령어를 미리 가져다 놓는 용도
- 구성하는 명령어의 형태에 따라 Control Unit ( 명령어를 해석하는 역할 ) 의 구조가 결정됨
- 여러가지 제약 사항들은 CPU의 종합적 성능이 고려되는 가운데 등장하게 됨
ex) 첫번째 피연산자 위치에는 레지스터 이름만 와야함
① CISC 구조 (Complex Istruction Set Computer)
- 복잡한 명령어 체계를 가지는 컴퓨터
- 다양한 명령어를 제공 -> 수십줄에 걸쳐 구현해야할 명령어를 단 한줄로 구성할 수 있음
- 메모리를 효율적으로 사용할 수 있음
but CPU구조가 복잡해져야 함 => 성능 향상에 제한
② RISC 구조 (Reduced Instruction Set Computer)
- CISC 구조에서 10%의 명령어만 주로 사용됨에 착안
- 명령어 수를 줄이고 길이를 일정하게 디자인
- 근래 CPU, 임베디드 환경에서 사용되는 대부분의 CPU
=> 클럭당 처리하는 명령어 수를 늘여줌 ( Pipelining ) => 성능 향상 ( 물론 초당 클럭수도 중요 )
II. LOAD & STORE 명령어 디자인
1. LOAD & STORE 명령어의 필요성
- 지금까진 " 사칙 연산 결과를 레지스터에만 저장할 수 있다 "는 제약을 둠
=> ∴ 피연산자에 메모리 주소를 사용할 수 없음
2. LOAD & STORE 명령어의 디자인
- 이전에 디자인 한 사칙 연산에 비해 피연산자가 두개(메인 메모리, 레지스터) 만 필요
- LOAD -> 110
ex) Load r3, 0x07
| 00 | 110 | 011 | 00000111 |
- STORE -> 111 // destination 과 source가 반대
ex) "c = a + b"
LOAD r1, 0x10
LOAD r2, 0x20
ADD r3, r1, r2
STORE r3, 0x30
III. Direct 모드와 Indirect 모드
1. Direct 모드의 문제점과 Indirect 모드의 제안
- 하나의 명령어에 여러 정보를 담음 -> 데이터 크기의 제한
ex) destination에는 레지스터가 8개나 되므로 문제가 없으나
source 부분에는 0x0000 ~ 0x00ff까지만 표현할 수 있음
- Direct 모드 : 할당된 비트 수 만큼의 메모리 영역만 접근 가능 => 주소값을 명령어에 '직접' 표현
2. Indirect 모드의 이해
- 명령어에서 지정하는 번지에 저장된 값을 주소값으로 참조하게 됨
3. Indirect 모드의 활용 예제
ex)
int a = 10; // 0x0010번지에 할당
int b = 20; // 0x0100번지에 할당 => Direct 모드로는 불가능
int c = 0; // 0x0020번지에 할당
c = a + b;
=====================
LOAD r1, 0x0010
MUL r0, 4, 4
MUL r2, 4, 4
MUL r3, r0, r2
STORE r3, 0x0030
(LOAD r2, r3는 사용 불가능 => source 부분에는 주소 정보만 사용가능 하기 때문)
LOAD r2, [0x0030] // 0x0030에는 0x0100이 저장되어 있음
ADD r3, r1, r2
참고 : 뇌를 자극하는 윈도우즈 시스템 프로그래밍. 윤성우 저
피드 구독하기:
글 (Atom)