본문 바로가기

04. 프로세스 - CreateProcess

by 식 2009. 8. 21.

BOOL CreateProcess함수를 이용하면 새로운 프로세스를 생성할 수 있다.

BOOL CreateProcess(
PCTSTR pszApplicationName,
PSECURITY_ATTRIBUTES psaProcess,
PSECURITY_ATTRIBUTES psaThread,
BOOL bInheritHandles,
DWORD fdwCreate,
PVOID pvEnvironment,
PCTSTR pszCurDir,
PSTARTUPINFO psiStartInfo,
PROCESS_INFORMATION ppiProcInfo);

스레드가 CreateProcess를 호출하면 시스템은 사용 카운트가 1인 프로세스 커널 오브젝트를 생성한다.
프로세스 커널 오브젝트는 프로세스 자체를 의미하는 것은 아니며, 운영체제가 프로세스를 관리하기 위한 목적으로 생성한 조그마한 데이터 구조체다. 프로세스 커널 오브젝트를 프로세스에 대한 각종 통계 정보를 가지고 있는 작은 데이터 구조체라고 생각할 수도 있다. 프로세스 커널 오브젝트가 생성되고 나면 시스템은 새로운 프로세스를 위한 가상 주소 공간을 생성하고, 실행 파일의 코드와 데이터 및 수행에 필요한 추가적인 DLL 파일들을 프로세스의 주소 공간 상에 로드한다.

다음 단계로, 시스템은 새로 생성된 프로세스의 주 스레드를 위한 스레드 커널 오브젝트(사용 카운트 1)를 생성한다. 주 스레드는 링커에 의해 진입점으로 지정된 C/C++ 런타임 시작 코드를 실행한다. 이러한 시작 코드는 종국에는 사용자가 작성한 WInMain, wWinMain, main, 또는 wmain함수를 호출하게 된다. 만일 시스템이 성공적으로 프로세스를 생성하고 주 스레드를 생성하였다면 CreateProces는 TRUE를 반환한다.

 ※ CreateProcess 함수는 새로 생성된 프로세스가 완전히 초기화되기 전에 TRUE를 반환한다. 이것은 운영체제의 로더가 새로 생성된 프로세스가 필요로 하는 모든 DLL을 로드하기 전에 CreateProcess가 반환될 수 있다는 의미이다. 만일 필요한 DLL이 없거나 올바르게 초기화가 진행되지 않으면 새로 생성된 프로세스는 곧바로 종료된다. 하지만 CreateProcess는 이미 TRUE를 반환했을 것이기 때문에 이 경우 부모 프로세스는 자식 프로세스에 어떠한 초기화 문제가 발생했는지 알 수 없다.

(1) pszApplicationName 과 pszCommandLine
 pszApplicationName과 pszCommandLine 매개변수로는 각각 프로세스를 생성할 실행 파일명과 새로운 프로세스에게 전달할 명령행 문자열을 지정하게 된다. 먼저 pszCommandLine 매개변수에 대해 알아보기로 하자
pszCommandLine 매개변수의 자료형이 PTSTR인 점에 주목할 필요가 있다. 이것은 우리가 전달하는 문자열이 CreateProcess 함수 내에서 변경될 수 있는 형태(non-constant string)로 전달되어야 함을 의미한다. CreateProcess는 내부적으로 우리가 전달하는 명령행 문자열에 변경 작업을 수행한다. 하지만 반환 직전에 그 내용을 원래의 값으로 돌려놓는다.

만일 명령행 문자열을 읽기 전용의 파일명 형태로 전달하게 되면 접근 위반이 발생하기 때문에 이 점은 매우 중요하다. 다음과 같이 코드를 작성하면 마이크로소프트 C/C++ 컴파일러는 "NOTEPAD"라는 문자열을 읽기 전용의 메모리에 배치하기 때문에 접근 위반을 유발하게 된다.

STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi;
CreateProcess(NULL, TEXT("NOTEPAD"), NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);

이러한 접근 위반은 CreateProcess가 내부적으로 전달된 문자열을 수정하려 할 때 발생하게 된다. (이전 버전의 마이크로소프트 C/C++ 컴파일러는 위와 같이 문자열을 전달하는 경우에도 읽기/쓰긱 ㅗ모두 가능한 메모리 상에 문자열을 배치하였으므로 CreateProcess를 호출하더라도 접근 위반을 유발하지 않았다. 이 문제를 해결하기 위한 최상의 방법은 다음의 예와 같이 문자열 상수를 임시 버퍼에 복사한 후 CreateProcess를 호출하는 것이다.

STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi;
TCHAR szCommandLine[] = TEXT("NOTEPAD");
CreateProcess(NULL, szCommandLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);

마이크로소프트 C/C++ 컴파일러는 중복 문자열을 제거하고 문자열을 읽기 전용 섹션에 배치시킬 수 있도록 /Gf와 /GF 컴파일러 스위치를 제공한다(Edit & Continue와 같은 Visual Studio 디버거의 기능을 활성화하기 위해 /ZI 컴파일 스위치를 사용하면 /GF 스위치를 수반하게 된다.) 이러한 문제를 해결하는 가장 좋은 방법은 /GF 컴파일러 스위치를 사용하되 임시 버퍼를 이용하는 것이다. 물론 마이크로스프트가 CreateProcess를 수정해서 개발자가 임시 버퍼를 사용하지 않아도 되도록 함수 내부에서 전달된 문자열의 복사본을 만들어서 사용하도록 하면 될 것이다. 차기 버전의 윈도우에서는 이와 같이 변경되기를 기대해 본다.

흥미로운 사실은 윈도우 비스타에서 ANSI 버전의 CreateProcess를 위와 같은 방식으로 호출할 때에는 접근 위반이 발생하지 않는다는 것이다. 이는 유니코드로의 변경을 위해 문자열에 대한 복사본이 내부적으로 만들어지기 때문이다.

CreateProcess의 두 번째 매개변수인 pszCommandLine 을 이용하면 CreateProcess 가 새로운 프로세스를 생성하기 위해 필요한 추가 정보를 제공할 수 있다. pszCommandLine을 통해 전달되는 문자열의 첫 번째 토큰은 실행하고자 하는 프로그램의 파일명으로 간주되며, 확장자가 전달되지 않으면 .exe로 가정한다. CreateProcess는 실행 파일을 찾기 위해 다음과 같이 순차적으로 검색을 진행한다.

1. 생성할 프로세스의 실행 파일명에 포함된 디렉토리
2. 생성할 프로세스의 현재 디렉토리
3. 윈도우 시스템 디렉토리. 즉 GetSYstemDirectory가 반환하는 System32 서브폴더
4. 윈도우 디렉토리
5. PATH 환경변수에 포함된 디렉토리들

물론 생성할 프로세스의 파일명이 전체 경로를 포함하고 있는 경우라면 이러한 전체 경로만을 이용하여 실행 파일을 찾게 되고 나머지 디렉토리에서는 검색을 수행하지 않는다. 시스템이 실행 파일을 찾으면 실행 파일의 코드와 데이터는 새로운 프로세스의 주소 공간에 매핑된다. 이후 링커에 의해 애플리케잉션 진입점으로 지정된 C/C++ 런타임 시작 함수를 호출한다. 앞서 말한 바와 강이 C/C++ 런타임 시작 함수는 프로세스의 명령행을 검토하여 실행 파일명 다음으로 전달되는 첫 번째 인자를 가리키는포인터를 (w)WinMain의 pszCmdLine 매개변수를 통해 전달된다.

pszApplicationName 매개변수를 NULL로 지정하는 한(대부분의 경우 NULL)로 지정 이와 같이 작업이 수행된다. 하지만 pszApplicationName 매개변수로 실행 파일명을 담고 있는 문자열의 주소를 전달할 수도 있다. pszApplication 매개변수로 파일명을 지정하는 경우 파일명의 확장자를 .exe로 가정하지 않기 때문에 반드시 확장자를 포함하도록 파일명을 지정해야 한다. 이 경우 Create-Process 는 파일명에 경로명이 없다면 파일이 현재 디렉토리 상에 있을 것이라고 가정하게 된다. 따라서 파일이 현재 디렉토리에 존재하지 않으면 CreateProcess는 다른 디렉토리를 검색하지 않으며 실패를 반환한다.

pszApplicationName 매개변수로 파일명을 지정하는 경우라 하더라도 pszCommandLine 매개변수를 통해 새로운 프로세스를 위한 명령행 문자열을 전달할 수 있다. 예를 들어 CreateProcess를 다음과 같이 사용하는 경우를 생각해 보자.

// 전달하는 메모리는 읽기/ 쓰기가 가능한 메모리 상에 위치해야 한다.
TCHAR szPath[] = TEXT("WORDPAD README.TXT");

// 새로운 프로세스를 생성한다.
CreateProcess(TEXT("C:\\WINDOWS\\SYSTEM32\\NOTEPAD.EXE"), szPath, ...);

위 코드는 메모장 NotePad을 수행한다. 하지만 메모장의 명령행에는 WORDPAD README.TXT가 전달된다. 이것이 이상해 보이지만 CreateProcess는 이와 같이 동작한다. 사실 pszApplicationName 매개변수를 통해 실행 파일명을 지정할 수 있도록 한 것은 CreateProcess가 윈도우의 POSIX 서브 시스템을 지원하도록 하기 위해 포함시킨 것이다.

(2) psaProcess, psaThread, bInheritHandles
새로운 프로세스를 생성하기 위해 시스템은 새로운 프로세스 커널 오브젝트와 스레드 커널 오브젝트(프로세스의 주 스레드)를 생성해야 한다. 2개의 오브젝트들은 모두 커널 오브젝트이므로 부모 프로세스는 각각에 대해 보안 특성을 지정할 수 있어야 한다. CreateProcess 함수에서는 psaProcess와 psaThread 매개변수를 통해 프로세스 커널 오브젝트와 스레드 커널 오브젝트 각각에 대해 원하는 보안 특성을 지정할 수 있다. 기본 보안 해설자를 사용하길 원한다면 각 매개변수를 NULL로 지정하면 된다. 그렇지 않은 경우라면 SECURITY_ATTRIBUTES 구조체를 생성하여 프로세스 오브젝트와 스레드 오브젝트 각각에 대해 적절한 보안 권한을 설정하면 된다.

psaProcess와psaThread 매개변수로 SECURITY_ATTRIBUTES를 사용하는 또 다른 이유는 두개의 커널 오브젝트 핸들을 상속 가능하도록 생성하여 추후 부모 프로세스가 새로운 자식 프로세스를 생성할 때 이 커널 오브젝트들을 사용할 수 있도록 하기 위함이다.

커널 오브젝트 핸들 상속을 보여주기 위한 간단한 프로그램이다. A 프로세스는 CreateProcess를 호출할 때 psaProcess 매개변수로 bInheritHandle 값을 TRUE로 설정한 SECURITY_ATTRTI

반응형

'' 카테고리의 다른 글

ERROR : Provisioning profile ‘*' can’t be found”  (0) 2010.07.13
Struct Size  (0) 2009.10.12
03. 프로세스  (0) 2009.08.21
02. 커널 오브젝트 下  (0) 2009.08.18
01. 커널 오브젝트 上  (0) 2009.08.18