앨리스의 토끼

javahawk.egloos.com

포토로그 마이가든 방명록



GetProcAddress - DLL에서 제공하는 함수를 사용하자 비전공 개발자

프로그램을 시작하고 조금 맛을 보다보면 DLL을 사용하게 될 경우를 참 많이 만나게 된다.
기본적으로 자신이 직접 모든 것을 구현해 낸다면 좋을 지 모르지만, 요즘같이 화려한 UI를 제공하며 복잡한 알고리즘을 가진 프로세스를 만들다보면 한계를 느끼게 될 수 밖에 없다.
그럴 때면 누군가 내가 필요로 하는 이 기능을 만들어 두지 않았을까 생각하게 되고 여기 저기 웹서핑을 하면서 이것저것 많은 정보를 찾을 것이다.
뭐 그러면 그런 대로 그런 과정을 통해서 개발자는 자신이 알지 못하던 것을 배우게 되고, 자신의 활동 분야를 더욱더 넓힐 수 있으니 굳이 반대할 이유는 없다.
간단한 기능의 경우는 정말 쉽게 함수 한두개를 만들어서 소스를 오픈해 둔 것이라도 찾으면 정말 쉽게 구현이 될 것이지만, 복잡한 기능은 그것이 쉽지 않다.
얘를 들어 윈도우에서 지원하는 현재 콘솔의 세션을 구하는 방법이나, 로그인 사용자의 토큰을 구하는 등의 계정 친화적인 기능이 필요한 경우에는 윈도우에서 제공하는 함수를 쓰지 않을 수가 없다.
물론 직접 구현해도 된다. 그러기 위해서는 요구하는 기능이 내부적으로 수행하는 기능부터 시작해서 OS에서 관리하는 사용자 관리의 내용을 뿌리까지 이해하면서 만들면 되는 일이다.
하지만, 귀찮다.
시간이 없다.
내가 왜 그 간단할 것 같은 기능을 하나 사용하려고, 윈도우의 뿌리를 이해하고 들춰내야 하는가?
OS라면 OS에서 기본적으로 그런 것들을 지원해야 하는 것 아닌가?
당연하다.
OS에서 지원한다.
하지만, 이것을 지원하기 위해 윈도우가 원시 코드를 모두 제공할 필요는 없다.
그것은 MS같은 OS 개발업자에게는 그다지 좋은 생각이 되지 못한다.
그렇다면 방법이 없나?
왜 그래?
이미 알고 있으면서...
그네들이 만든 함수를 제공하는 DLL을 가져다 사용하면 된다.
물론 그런 함수를 제공하는 것이 DLL 만은 아니다.
실행파일에서도 함수를 제공할 수 있다.
뭐 실행파일이면서 함수를 제공한다고 하면, 기본적으로 서비스를 예로 들 수 있다.
서비스라고 하면 분명 EXE 파일이지만, 제공하는 기능은 분명 DLL이 포함되어 있다.
물론 VS에서 기본적으로 만드는 서비스가 제공하는 기능은 일반의 Win32 DLL이 제공하는 기능이 아니라 ATL COM과 같은 Object 개념으로 제공하는 것이라 사용하는 방법에 분명 차이가 있다.
그렇다면 일단은 가장 기본이 되는 Win32 API를 사용하는 방법을 알아보자.
그중에서도 오늘은 특별한 함수를 사용하여 함수의 포인터를 얻어오는 방법을 사용해 보고자 한다.

FARPROC GetProcAddress(
    HMODULE hModule,
    LPCSTR lpProcName
);

이 함수의 반환값을 보면 FARPROC으로 되어 있다. 이 FARPROC으로 반환되는 값이 함수의 포인터이다.
왜 이런 함수의 포인터를 받는 것일까?
간단하다.
그 DLL이 없거나, 그 DLL에서 해당하는 기능을 제공하지 않을 경우가 있기 때문이다.
귀찮다.
몇명이나 본다고...
길게 쓰기 싫으니까 더 이상의 설명은 줄이고 사용법이다.
아... 역시 게으른 종족은 이런 것도 힘들어...

hModule에는 메모리에 로드된 DLL에 대한 핸들을 준다.
물론 DLL이 아니라 실행 파일일 수도 있다.
여기서 핸들을 얻기 위한 방법은 대개 LoadLibrary 매크로를 사용한다.
LoadLibrary 매크로는 유니코드 여부에 따라 LoadLibraryW나 LoadLibraryA 함수로 치환된다.
LoadLibraryA는 LPCSTR형태의 lpLibFileName이라는 하나의 인자를 받는다.
이 인자에 DLL 파일의 파일명을 주면, LoadLibraryA는 HMODULE 형으로 핸들값을 반환한다.
이 핸들값을 GetProcAddress 함수의 첫번째 인자로 주면된다.
다음은 winbase.h 파일에 있는 LoadLibraryA와 LoadLibraryW 함수의 선언부이다.

WINBASEAPI
__out
HMODULE
WINAPI
LoadLibraryA(
    __in LPCSTR lpLibFileName
    );
WINBASEAPI
__out
HMODULE
WINAPI
LoadLibraryW(
    __in LPCWSTR lpLibFileName
    );

다음은 LoadLibrary 매크로의 정의이다.

#ifdef UNICODE
#define LoadLibrary  LoadLibraryW
#else
#define LoadLibrary  LoadLibraryA
#endif // !UNICODE

그러면 우선 Kernel.dll 파일의 핸들을 얻어보자.

HMODULE hKernel = LoadLibrary( "Kernel32.dll" );

사용법은 의외로 간단하다.

다음으로 살펴 볼 것은 LPCSTR 형으로 지정된 lpProcName 변수이다.
이 변수는 type으로도 알 수 있듯이 함수의 이름을 나타내는 문자열이다.

이제 중요한 것은 반환받을 함수 포인터이다.
함수라는 것이 워낙 다양한 형태로 만들어지기 때문에, 적어도 우리는 우리가 호출할 함수의 기본형에 대해서는 알고 있어야 한다.
여기서는 다음의 함수를 예로 들어보자.

DWORD WTSGetActiveConsoleSessionId(void);

이 함수는 인자를 받지 않고 호출하여 DWORD값을 반환한다.
이런 경우의 함수 포인터 사용을 위한 기본형 선언부는 다음과 같이 선언할 수 있다.

typedef DWORD ( WINAPI * WTSGetActiveConsoleSessionIdFunc )();

이것은 인자를 받지 않고 결과값은 DWORD를 반환하는 함수포인터의 기본형을 선언한 것이다.
이제 GetProcAddress를 호출하여 이 선언부에 중요한 함수 포인터를 받아보자.

WTSGetActiveConsoleSessionIdFunc WTSGetActiveConsoleSessionId = ( WTSGetActiveConsoleSessionIdFunc )GetProcAddress( hKernel, "WTSGetActiveConsoleSessionId" );

이것으로 이제 우리는 WTSGetActiveConsoleSessionId 함수를 사용할 수 있게 되었다.

DWORD dwRet = WTSGetActiveConsoleSessionId();

물론 이런 함수를 가져올 때 인자를 1개 이상 사용하는 함수에 대해서도 대동소이한 방법으로 사용이 가능하다.

Wtsapi32.dll 파일이 제공하는 WTSQueryUserToken 함수를 예로 들어보자.

HMODULE hWtsapi = LoadLibrary( "Wtsapi32.dll" );
typedef BOOL ( WINAPI * WTSQueryUserTokenFunc )( ULONG SessionId, PHANDLE phToken );
WTSQueryUserTokenFunc WTSQueryUserToken = ( WTSQueryUserTokenFunc )GetProcAddress( hWtsapi, "WTSQueryUserTokenFunc" );

공유하기 버튼

 
 

RegisterDeviceNotification를 사용한 디바이스 감지 비전공 개발자

보통 Win32 API를 사용하는 수준에서 장치의 연결과 해제를 통보받기 위해 사용하는 함수로 RegisterDeviceNotification라는 함수를 사용한다.
이 함수를 사용하면 자신이 통보를 받기 원하는 장치의 GUID 값과 이벤트를 받아서 처리할 윈도우나 서비스의 핸들을 알아야 한다.

HDEVNOTIFY WINAPI RegisterDeviceNotification(
  __in  HANDLE hRecipient,
  __in  LPVOID NotificationFilter,
  __in  DWORD Flags
);

사용하기 위한 필요조건은 다음과 같다.

Library - User32.lib
DLL - User32.dll
Header - Winuser.h(include Windows.h)
Minimum supported client - Windows XP
Minimum supported server - Windows Server 2003
For Unicode - RegisterDeviceNotificationW
For ANSI - RegisterDeviceNotificationA

Application이라면 BroadcastSystemMessage 함수를 호출하여 메시지를 보낼 수 있고, WM_DEVICECHANGE 메시지를 통해 정보를 받을 수 있다.
RegisterDeviceNotification은 device의 감지메시지를 받기 위해 application이 사용하는 감지자 등록 기능을 수행한다. 물론 service의 경우에도 동일하게 사용이 가능하다.

MSDN에서 제공하는 샘플 예제 중의 중요 코드 부분만 사용하여 예제를 보인다.

이벤트를 위한 받기 위한 준비는 다음과 같다.


// This GUID is for all USB serial host PnP drivers, but you can replace it
// with any valid device class guid.
GUID WceusbshGUID = { 0x25dbce51, 0x6c8f, 0x4a72, 0x8a,0x6d,0xb5,0x4c,0x2b,0x4f,0xc8,0x35 };

DEV_BROADCAST_DEVICEINTERFACE NotificationFilter;

ZeroMemory( &NotificationFilter, sizeof( NotificationFilter ) );
NotificationFilter.dbcc_size = sizeof( DEV_BROADCAST_DEVICEINTERFACE );
NotificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
NotificationFilter.dbcc_classguid = WceusbshGUID;

HDEVNOTIFY hDeviceNotify = RegisterDeviceNotification(
 hWnd,                       // events recipient
 &NotificationFilter,        // type of device
 DEVICE_NOTIFY_WINDOW_HANDLE // type of recipient handle
 );


이렇게 실행하여 성공하면 hDeviceNotify 값은 NULL이 아니게 되면 hWnd로 지정한 recipient에서는 Procedure를 통해 WM_DEVICECHANGLE 메시지를 받을 수 있다.


switch( message )
{
case WM_DEVICECHANGE:
 {
  PDEV_BROADCAST_DEVICEINTERFACE b = (PDEV_BROADCAST_DEVICEINTERFACE) lParam;
  TCHAR strBuff[256];

  // Output some messages to the window.
  switch (wParam)
  {
  case DBT_DEVICEARRIVAL:
   msgCount++;
   StringCchPrintf( strBuff, 256, TEXT("Message %d: DBT_DEVICEARRIVAL\n"), msgCount);
   break;
  case DBT_DEVICEREMOVECOMPLETE:
   msgCount++;
   StringCchPrintf( strBuff, 256, TEXT("Message %d: DBT_DEVICEREMOVECOMPLETE\n"), msgCount);
   break;
  case DBT_DEVNODES_CHANGED:
   msgCount++;
   StringCchPrintf( strBuff, 256, TEXT("Message %d: DBT_DEVNODES_CHANGED\n"), msgCount);
   break;
  default:
   msgCount++;
   StringCchPrintf( strBuff, 256, TEXT("Message %d: WM_DEVICECHANGE message received, value %d unhandled.\n"), msgCount, wParam);
   break;
  }
 }


Application을 종료할 때는 반드시 등록된 recipient 정보를 해제해 준다. 이때 사용하는 인자는 RegisterDeviceNotification에서 반환한 반환값을 사용한다.

i
f ( ! UnregisterDeviceNotification(hDeviceNotify) )
{
 ErrorHandler(TEXT("UnregisterDeviceNotification"));
}


RegisterDeviceNotification 상세설명

공유하기 버튼

 
 

IRP_MJ_DIRECTORY_CONTROL 비전공 개발자

User-mode application의 경우에는 다음의 함수들의 호출에 기인한다.
ReadDirectoryChangesW, FindNextVolumeMountPoint

Kernel-mode component의 경우에는 다음의 native API의 호출에 기인한다.
ZwQueryDirectoryFile


I. IrpSp->MinorFunction은 다음의 두 가지로 분류된다.

 1. IRP_MN_NOTIFY_CHANGE_DIRECTORY
 - Directory에 변경이 가해졌을 경우에 감지된다.

 2. IRP_MN_QUERY_DIRECTORY
 - Directory query요청에 대하여 다음의 9가지 유형으로 분류된다.

  1) FileBothDirectoryInformation
  2) FileDirectoryInformation
  3) FileFullDirectoryInformation
  4) FileIdBothDirectoryInformation
  5) FileIdFullDirectoryInformation
  6) FileNamesInformation
  7) FileObjectIdInformation
  8) FileReparsePointInformation

  9) FileQuotaInformation도 있는데, IRP_MJ_QUERY_QUOTA로 별도 분리되었단다.


II. IrpSp->FileObject
- FILE_OBJECT구조체의 RelatedFileObject 필드의 포인터를 포함한다. 이 포인터는 열려있는 파일에 대한 정보이다.
- 자세한 정보는 FILE_OBJECT 구조체를 확인한다.


III. IrpSp->Flags
- 이 때의 Flags는 다음의 4가지 값이 가능하다.


 1. SL_INDEX_SPECIFIED
 - 이 값의 경우 IrpSp->Parameters.QueryDirectory.FileIndex에 의해 주어진 디렉토리 인덱스의 entry에서 scanning을 시작한다.


 2. SL_RESTART_SCAN
 - 이 경우에는 디렉토리의 first entry부터 scan을 한다.
 - 만약 이 값이 설정되지 않았다면 이전의 IRP_MN_QUERY_DIRECTORY 요청으로부터 다시 scanning을 시작한다.
 - 말이 어려운데 MSDN에 소개된 내용은 다음과 같다.
 - If this flag is not set, resume the scan from a previous IRP_MN_QUERY_DIRECTORY request.


 3. SL_RETURN_SINGLE_ENTRY
 - 이건 간단하다. first entry 만을 반환한다.
 - Return only the first entry that is found.

 이에 더해서 IRP_MN_NOTIFY_CHANGE_DIRECTORY에 대해서만 다음의 한 가지가 더 가능하다.


 4. SL_WATCH_TREE
 - 이 경우에는 subdirectory에 대한 검색 여부에 따라서 TRUE, FALSE를 설정한다(?)


IV. IrpSp->Parameters.NotifyDirectory.CompletionFilter
- FsRtlNotifyFullChangeDirectory 함수의 CompletionFilter의 설명을 보란다.
- 이것에 대한 내용이 상당히 많다. 다음에 다뤄본다.


V. IrpSp->Parameters.NotifyDirectory.Length
- Irp->UserBuffer로 지정된 버퍼의 사이즈


VI. IrpSp->Parameters.QueryDirectory.FileIndex
- SL_INDEX_SPECIFIED가 필수이다. 없으면 무의미한 값이 된다.
- 이 경우에는 Win32함수나 kernel-mode support routine에서 지정될 수 없다.
- 대개는 NTVDM(NT virtual DOS machine)에서 사용된다.
- NTFS 같은 경우에는 sorting order에 따라 변경이 가능하다.


VII. IrpSp->Parameters.QueryDirectory.FileInformationClass
- 이건 위의 IRP_MN_QUERY_DIRECTORY에서 나열된 9가지 중의 하나가 될 수 있다.
- 각각의 클래스에 대해 파일별로 다음의 구조체를 반환받는다.
- 해당하는 각 구조체들에 대한 정보는 IRP의 UserBuffer로부터 형 변환으로 받아올 수 있다.


 1. FileBothDirectoryInformation - FILE_BOTH_DIR_INFORMATION
 - 포함된 모든 시간은 시스템의 절대 시간을 기준으로 한다.
 - 절대 시간은 1601년을 시작으로 하는 100-nano 초 단위의 간격으로 매겨진다.
 - 사용하는 헤더는 Ntifs.h(또는 Fltkernel.h)를 사용한다.


typedef struct _FILE_BOTH_DIR_INFORMATION {
 ULONG         NextEntryOffset;
 // 다음의 FILE_BOTH_DIR_INFORMATION의 offset. 만약 다음 대상이 없으면 NULL값이 된다.
 ULONG         FileIndex;
 // 부모 디렉토리 기준의 파일에 대한 byte offset. NTFS에서는 sorting order에 따라 변경된다.
 LARGE_INTEGER CreationTime;        // 파일 생성 시간
 LARGE_INTEGER LastAccessTime;   // 파일 접근 시간
 LARGE_INTEGER LastWriteTime;       // 최종적으로 쓰여진 시간 시간
 LARGE_INTEGER ChangeTime;         // 파일이 변경된 시간
 LARGE_INTEGER EndOfFile;             // byte offset으로 지정된 포지션.
 LARGE_INTEGER AllocationSize;
 // File allocation size, in bytes. 이 경우에는 물리 디바이스의 섹터나 클러스터 사이즈에 따라 multiple하다.
 ULONG         FileAttributes;       // 이 값은 여러값의 조합이다. 가능한 조합은 아래에 나열한다.
 ULONG         FileNameLength;  // byte 단위
 ULONG         EaSize;               // 파일의 EA(Extended attributes)에 대한 바이트단위 사이즈
 CCHAR         ShortNameLength;
 WCHAR         ShortName[12];   // 8.3 기준 short name의 Unicode string
 WCHAR         FileName[1];       // 파일명의 첫 문자로 지정
} FILE_BOTH_DIR_INFORMATION, *PFILE_BOTH_DIR_INFORMATION;


 - FileAttributes의 가능 조합
 FILE_ATTRIBUTE_READONLY
 FILE_ATTRIBUTE_HIDDEN
 FILE_ATTRIBUTE_SYSTEM
 FILE_ATTRIBUTE_DIRECTORY
 FILE_ATTRIBUTE_ARCHIVE
 FILE_ATTRIBUTE_DEVICE
 FILE_ATTRIBUTE_NORMAL
 FILE_ATTRIBUTE_TEMPORARY
 FILE_ATTRIBUTE_SPARSE_FILE
 FILE_ATTRIBUTE_REPARSE_POINT
 FILE_ATTRIBUTE_COMPRESSED
 FILE_ATTRIBUTE_OFFLINE
 FILE_ATTRIBUTE_NOT_CONTENT_INDEXED
 FILE_ATTRIBUTE_ENCRYPTED


 2. FileDirectoryInformation - FILE_DIRECTORY_INFORMATION
 - 포함된 모든 시간은 시스템의 절대 시간을 기준으로 한다.
 - 절대 시간은 1601년을 시작으로 하는 100-nano 초 단위의 간격으로 매겨진다.
 - 사용하는 헤더는 Ntifs.h(또는 Fltkernel.h)를 사용한다.
 - 중복된 필드에 대한 설명은 제외한다.


typedef struct _FILE_DIRECTORY_INFORMATION {
 ULONG         NextEntryOffset;
 ULONG         FileIndex;
 LARGE_INTEGER CreationTime;
 LARGE_INTEGER LastAccessTime;
 LARGE_INTEGER LastWriteTime;
 LARGE_INTEGER ChangeTime;
 LARGE_INTEGER EndOfFile;
 LARGE_INTEGER AllocationSize;
 ULONG         FileAttributes;
 ULONG         FileNameLength;  // 파일명의 길이
 WCHAR         FileName[1];  // 파일명의 첫 문자로 지정
} FILE_DIRECTORY_INFORMATION, *PFILE_DIRECTORY_INFORMATION;


 - FileAttributes의 가능 조합
 FILE_ATTRIBUTE_READONLY
 FILE_ATTRIBUTE_HIDDEN
 FILE_ATTRIBUTE_SYSTEM
 FILE_ATTRIBUTE_DIRECTORY
 FILE_ATTRIBUTE_ARCHIVE
 FILE_ATTRIBUTE_NORMAL
 FILE_ATTRIBUTE_TEMPORARY
 FILE_ATTRIBUTE_COMPRESSED


 3. FileFullDirectoryInformation - FILE_FULL_DIR_INFORMATION
 - 포함된 모든 시간은 시스템의 절대 시간을 기준으로 한다.
 - 절대 시간은 1601년을 시작으로 하는 100-nano 초 단위의 간격으로 매겨진다.
 - 사용하는 헤더는 Ntifs.h(또는 Fltkernel.h)를 사용한다.
 - 중복된 필드에 대한 설명은 제외한다.


typedef struct _FILE_FULL_DIR_INFORMATION {
 ULONG         NextEntryOffset;
 ULONG         FileIndex;
 LARGE_INTEGER CreationTime;
 LARGE_INTEGER LastAccessTime;
 LARGE_INTEGER LastWriteTime;
 LARGE_INTEGER ChangeTime;
 LARGE_INTEGER EndOfFile;
 LARGE_INTEGER AllocationSize;
 ULONG         FileAttributes;
 ULONG         FileNameLength;
 ULONG         EaSize;
 WCHAR         FileName[1];
} FILE_FULL_DIR_INFORMATION, *PFILE_FULL_DIR_INFORMATION;


 - FileAttributes의 가능한 값은 다음과 같다.
 FILE_ATTRIBUTE_READONLY
 FILE_ATTRIBUTE_HIDDEN
 FILE_ATTRIBUTE_SYSTEM
 FILE_ATTRIBUTE_DIRECTORY
 FILE_ATTRIBUTE_ARCHIVE
 FILE_ATTRIBUTE_NORMAL
 FILE_ATTRIBUTE_TEMPORARY
 FILE_ATTRIBUTE_COMPRESSED


 4. FileIdBothDirectoryInformation - FILE_ID_BOTH_DIR_INFORMATION
 - 포함된 모든 시간은 시스템의 절대 시간을 기준으로 한다.
 - 절대 시간은 1601년을 시작으로 하는 100-nano 초 단위의 간격으로 매겨진다.
 - 사용하는 헤더는 Ntifs.h(또는 Fltkernel.h)를 사용한다.
 - 중복된 필드에 대한 설명은 제외한다.


typedef struct _FILE_ID_BOTH_DIR_INFORMATION {
 ULONG         NextEntryOffset;
 ULONG         FileIndex;
 LARGE_INTEGER CreationTime;
 LARGE_INTEGER LastAccessTime;
 LARGE_INTEGER LastWriteTime;
 LARGE_INTEGER ChangeTime;
 LARGE_INTEGER EndOfFile;
 LARGE_INTEGER AllocationSize;
 ULONG         FileAttributes;
 ULONG         FileNameLength;
 ULONG         EaSize;
 CCHAR         ShortNameLength;
 WCHAR         ShortName[12];
 LARGE_INTEGER FileId;
 // 파일 시스템에서 생성하여 배정한 8 byte file reference number.
 // Win 2K를 위해 NTFS에 추가된 16-byte의 "file objectID와는 다른 값이며,
 // 최적화 등에 의해서 변질될 수 있는 값이지만, 항상 unique하게 지정된다.
 WCHAR         FileName[1];
} FILE_ID_BOTH_DIR_INFORMATION, *PFILE_ID_BOTH_DIR_INFORMATION;


 - FileAttributes의 가능한 값은 다음과 같다.
 FILE_ATTRIBUTE_READONLY
 FILE_ATTRIBUTE_HIDDEN
 FILE_ATTRIBUTE_SYSTEM
 FILE_ATTRIBUTE_DIRECTORY
 FILE_ATTRIBUTE_ARCHIVE
 FILE_ATTRIBUTE_NORMAL
 FILE_ATTRIBUTE_TEMPORARY
 FILE_ATTRIBUTE_COMPRESSED


 5. FileIdFullDirectoryInformation - FILE_ID_FULL_DIR_INFORMATION
 - 포함된 모든 시간은 시스템의 절대 시간을 기준으로 한다.
 - 절대 시간은 1601년을 시작으로 하는 100-nano 초 단위의 간격으로 매겨진다.
 - 사용하는 헤더는 Ntifs.h(또는 Fltkernel.h)를 사용한다.
 - 중복된 필드에 대한 설명은 제외한다.


typedef struct _FILE_ID_FULL_DIR_INFORMATION {
 ULONG         NextEntryOffset;
 ULONG         FileIndex;
 LARGE_INTEGER CreationTime;
 LARGE_INTEGER LastAccessTime;
 LARGE_INTEGER LastWriteTime;
 LARGE_INTEGER ChangeTime;
 LARGE_INTEGER EndOfFile;
 LARGE_INTEGER AllocationSize;
 ULONG         FileAttributes;
 ULONG         FileNameLength;
 ULONG         EaSize;
 LARGE_INTEGER FileId;
 WCHAR         FileName[1];
} FILE_ID_FULL_DIR_INFORMATION, *PFILE_ID_FULL_DIR_INFORMATION;


 - FileAttributes의 가능한 값은 다음과 같다.
 FILE_ATTRIBUTE_READONLY
 FILE_ATTRIBUTE_HIDDEN
 FILE_ATTRIBUTE_SYSTEM
 FILE_ATTRIBUTE_DIRECTORY
 FILE_ATTRIBUTE_ARCHIVE
 FILE_ATTRIBUTE_NORMAL
 FILE_ATTRIBUTE_TEMPORARY
 FILE_ATTRIBUTE_COMPRESSED


 6. FileNamesInformation - FILE_NAME_INFORMATION
 - 사용하는 헤더는 Ntifs.h(또는 Fltkernel.h)를 사용한다.
 - 중복된 필드에 대한 설명은 제외한다.


typedef struct _FILE_NAMES_INFORMATION {
 ULONG NextEntryOffset;
 ULONG FileIndex;
 ULONG FileNameLength;
 WCHAR FileName[1];
} FILE_NAMES_INFORMATION, *PFILE_NAMES_INFORMATION;


 7. FileObjectIdInformation - FILE_OBJECTID_INFORMATION
 - NTFS에서는 object ID에 file reference number를 더해서 파일을 열어볼 수 있도록 허용한다.
 - File system filter drivers that use file object IDs should be tested for interoperability with DFS, the Replicator service, and the Distributed Link Tracking service, all of which use and manipulate file object IDs.
 - 사용하는 헤더는 Ntifs.h(또는 Fltkernel.h)를 사용한다.
 - 중복된 필드에 대한 설명은 제외한다.


typedef struct _FILE_OBJECTID_INFORMATION {
 LONGLONG FileReference;  // 8-byte file reference number. NTFS에서 파일 생성될 때마다 자동으로 생성한다.
 UCHAR    ObjectId[16];  // 16-byte file object ID. 드라이버나 어플리케이션의 요청에 의해 NTFS가 생성하는 할당한다. 해당 파일이 있는 volume에서 unique하다.
 union {
  struct {
   UCHAR BirthVolumeId[16];  // 파일이 있는 volume의 object ID. 복사, 이동 등의 파일작업 후에는 같은 값을 유지하지 못한다.
   UCHAR BirthObjectId[16];  // 파일 생성되었을 때의 파일의 object ID.
   UCHAR DomainId[16];  // Reserved; 0
  };
  UCHAR  ExtendedInfo[48];  // User-provided data. 위의 세가지 멤버를 사용할 수 있으며, 별도의 구조체를 선언할 수 있다.
 };
} FILE_OBJECTID_INFORMATION, *PFILE_OBJECTID_INFORMATION;


 8. FileReparsePointInformation - FILE_REPARSE_POINT_INFORMATION
 - 이것의 경우에는 파일이 아닌 디렉토리에 대한 구조체이다.
 - 사용하는 헤더는 Ntifs.h(또는 Fltkernel.h)를 사용한다.


typedef struct _FILE_REPARSE_POINT_INFORMATION {
 LONGLONG FileReference;
 ULONG    Tag;    // Reparse point tag. The ReparseTag member indicates the structure of the user-defined reparse data
} FILE_REPARSE_POINT_INFORMATION, *PFILE_REPARSE_POINT_INFORMATION;


 9. FileQuotaInformation
 - 이 클래스는  IRP_MJ_QUERY_QUOTA로 대치되어 별도의 구조체가 없다.


VIII. IrpSp->Parameters.QueryDirectory.FileName
- Optional name of a file within the specified directory
- 별다른 설명이 없다.


IX. IrpSp->Parameters.QueryDirectory.Length
- Irp->UserBuffer로 지정된 버퍼의 길이

공유하기 버튼

 
 

WinDbg를 이용한 VMWare의 가상머신 Kernel debugging - WinDbg

그러면 이번에는 실제 디버그 정보를 받아볼 WinDbg를 실행해 보자.
우선 WDK에 포함된 WinDbg를 실행해 보자.
나같은 경우에는 Debugging Tools for Windows (x64)라는 메뉴에 담긴 것을 실행했다.

WinDbg를 사용하여 Debuggee와 연결할 때는 비교적 상세한 정보를 받아서 보기를 원할 것이다.
다음으로 커널 디버깅을 하기 위한 메뉴를 선택한다.
메뉴에서 Kernel Debug라는 항목을 선택해 준다.

그러면, Kernel Debugging이라는 창이 나타난다.
이 창에서는 실제 VMWare에서 맞춰준 Debug 정보 내용을 확인하여 채워주면 된다.
우선 여기서는 COM2 포트를 가장하여 xcom이라는 pipe를 통해 받기로 이전에 설정했던 설정을 그대로 따라 맞춰준다.
Serial 통신으로 BR은 115200이었으니 이 또한 동일하게 맞춰준다.
Serial 통신에서 Baud Rate가 틀어질 경우 전문이 깨져서 나타나는 현상은 다반사이기 때문에 반드시 확인이 필요하다.
또한 pipe 통신을 사용할 것이기 때문에 Pipe 체크박스를 체크해 준다.
디버기 환경의 OS가 재부팅될 때 OS 부팅 과정의 디버그 정보까지 확인할 수 있도록 Reconnect를 체크해 준다.
확인을 눌러준다.

이제 디버깅 정보를 받을 준비가 모두 끝났다.
VMWare에서 Debuggee의 환경 정보까지 모두 마쳐졌다면, 이제 해당 OS를 실행시키면 된다.
디버그 환경에서 OS를 기동하면 역시 평소보다 기동되는 시간이 오래 걸린다.


일단 Ctrl-Break를 사용해서 디버기와 연결된 상태에서 정보를 잡아 보았다.
OS를 디버깅할 때는 상세한 정보를 확인할 수 있도록 MS에서 pdb 심볼 파일을 받을 수 있도록 제공해 주고 있다.
Symbol 파일을 받을 경로를 지정하면 실제 커널 단에서 수행되는 함수의 이름과 변수까지도 확인이 가능하다.
File - Symbol File Path를 선택하여 나타난 창에 심볼 파일 저장 경로와 MS 사이트를 주소를 포함해서 기록해 주면 된다.
위의 그림에서는 SRV*D:\DsSymbols*http://msdl.microsoft.com/download/Symbols;D:\DsSymbols 과 같이 설정된 것을 확인할 수 있다.
물론 이에 대해서는 커맨드 라인에서 명령어를 입력하여 지정 및 확인할 수 있다.
디버그 창에 나타난 정보들을 유심히 보면 여러가지 정보들을 알 수 있다.
특별히 *를 사용하여 작성된 창에서는 실행 중 프로세스를 중지시키고 다시 실행시키는 기능에 대한 설명이 포함되어 있다.
또한, 그 위의 정보를 살펴보면 현재 디버기의 환경의 정보를 몇 가지 확인해 볼 수 있다.
여기서 밑의 kd>로 표시된 커맨드 라인에 g를 입력하고 Enter를 치거나, F5키를 눌러서 다음 단계를 계속 실행시킬 수 있다.


커맨드 라인이 *BUSY*로 표시되면서 Debuggee is running...이라고 나타나고 있다.
이제 필요할 때 마다 Ctrl-Break로 멈춰서 원하는 정보를 확인할 수 있다.
Visual Studio를 사용하여 디버깅을 해본 사람들이라면 물론 한단계씩 진행하던 F10키를 요구할 수도 있다.
이것 또한 동일하게 제공되며, 이에 대한 커맨드 라인의 명령어는 p이다.
다음으로 브레이크 포인트를 잡아보자.


Break point를 잡기 위한 명령어는 bp이다.
화면처럼 bp nt!SwapContext라고 입력하면, 커널에서 사용하는 SwapContext 함수의 시작 부분에 bp를 설정할 수 있다.
물론 화면에 보이는 것과 같이 nt!KeUpdateSystemTime+0x38f 와 같은 포지션도 bp로 설정할 수 있다.
bp를 설정했다면, 이제 설정한 bp의 전체 리스트를 확인해 보자.
커맨드 라인에 bl을 입력하여 실행한다.
화면의 하단에 나타난 정보가 현재 설정된 bp들이다.
해당하는 소스의 위치와 포지션의 라인넘버까지 확인이 가능하다.
bp를 설정하면 역시 g를 입력 및 실행하여 해당 포인트에 대한 동작 직전 멈추는 것이 가능해 진다.
설정된 bp를 지우는 데에 사용되는 명령은 bc이다.

bc를 입력할 때는 bl로 확인하여 가장 앞에 나타난 인덱스 번호를 확인하여 입력해 준다.
그리고, 다시 bl을 실행하면 0번 인덱스로 설정되었던 내용이 삭제된 것을 확인할 수 있다.
WinDbg의 화면 상단에 나타난 Tool bar의 아이콘들을 선택하여 별도의 창을 열어서 확인할 수 있다.
물론 Alt-Number의 바로 가기 기능을 사용하여 창을 불러올 수도 있다.
Call Stack - Alt-6


Disassembly - Alt-7

Locals - Alt-3


Memory - Alt-5


Process and Threads - Alt-9



Registers - Alt-4


Watch - Alt-2

이 창들의 기능은 Visual Studio와 동일하기 때문에 이에 대한 별도의 설명은 생략한다.

실행 환경에서 자신이 배포한 프로그램에 대한 상세한 디버깅을 원한다면 소스 파일은 아니더라도 pdb 파일은 남겨두는 것이 엉뚱한 문제로 열을 내지 않아도 되는 방법이 될 것이다.

공유하기 버튼

 
 

WinDbg를 이용한 VMWare의 가상머신 Kernel debugging - Debuggee 설정 비전공 개발자

이런 건 일사천리로 뽑아줘야 제맛이지...

이번에는 Debuggee의 환경에서 맞춰 줄 차례이다.
일단 알고 넘어가야 할 것은 이전 글에서 나는 Debuggee OS의 COM 포트를 2번으로 잡았다는 점이다.
언제 그랬냐고 물을 것 같아서 얘기하지만, VMWare에서 Serial Port 2라고 잡히는 것은 COM2 포트가 할당되었다는 것이다.
결국 Debuggee PC의 장치관리자에서는 COM2 포트가 확인될 것이고, 여기를 통해서 제공되는 debug 정보는 \\.\pipe\xcom 이라는 파이프를 통해서 Debugger에 제공될 것이다.

그러면 이제 실제로 Debuggee에서 COM2 포트로 디버그 정보를 날리도록 설정해 보자.
미리 말해두지만, 여기서 설정하는 bcdedit나 msconfig는 Vista급에서는 지원되지만, 2000 등에서는 지원되지 않는다.
정확히 어떤 OS를 기준으로 지원되는지는 확인해 본 바가 없다.
만약 debug를 꼭 해야 하지만, bcdedit가 지원되지 않을 경우에는 널리 사용되고 있는 boot.ini 파일의 수정 방법을 추천한다.
이것에 대해서는 다음에 그 방법을 정리해 보도록 하겠다.

시작단추의 프로그램 중 '명령 프롬프트'를 '관리자 권한으로 실행'하도록 하자.
명령 프롬프트가 안보인다는 말은 하지 말자...
그것까지 찾아달라면 도둑놈이다...

분명 해당 화면의 타이틀바에 '관리자: 명령 프롬프트'라고 기록된 것이 보인다.
여기서 bcdedit를 실행해 보자.

가장 아래 보이는 항목 중 debug 항목이 'NO'인 것을 확인할 수 있다.

bcdedit /debug ON

이것을 입력하면 Debug 옵션이 켜진다.
이제 구체적인 COM2 포트 설정을 해보자.

bcdedit /dbgsettings serial debutport:2 baudrate:115200

을 입력한다.
'작업을 완료했습니다."
라는 메시지를 확인할 수 있다.

bcdedit /dbgsettings
를 입력하면 현재 설정된 debug 환경을 확인할 수 있다.

이제 '명령 프롬프트'를 닫는다.

다른 방법으로도 debug 정보가 설정된 내용을 확인할 수 있다.
'실행' 창에서 'msconfig'를 입력하여 실행시킨다.

나타나는 화면은 '시스템 구성' 창이다.
여기서 '부팅'탭을 선택하면 현재 OS에서 지원하는 부팅 모드를 확인할 수 있다.
이것의 자세한 사용법은 다음에 다뤄보자.

이 화면에서는 부팅에 대한 여러가지 옵션을 줄 수 있다.
중간 쯤에 있는 '부팅 로그'를 선택하면 부팅시의 커널 로그도 확인할 수 있을 듯...
나도 확인은 안해 봤다.
어쨌거나 중간 쯤에 있는 '고급 옵션'을 선택한다.
화면을 통해 내용들을 모두 확인할 수 있을 것이다.
상단의 체크박스 중 '디버그'가 선택된 것을 확인할 수 있다.

'전역 디버그 설정'에서 지정된 포트가  'COM2'라는 것과 '전송 속도'가 '115200'으로 설정된 것을 확인할 수 있다.
물론 여기서 값을 변경하면 반영이 되기 때문에 '시스템 구성'창을 닫을 때 변경된 내용의 반영을 위해 부팅이 필요하다는 메시지를 출력한다.
여기서 변경한 내용은 bcdedit를 통해서도 확인할 수 있다.
결국 bcdedit가 됬건 msconfig가 됬건 둘 중의 어느 쪽이라도 변경을 가하면 다른 쪽에서도 변경된 내용을 확인할 수 있다는 말이다.

참고로 msconfig에서 디버그 포트를 'USB'로 맞추면 'USB 대상 이름'을 지정해 줘야 해당하는 USB로 디버그 정보가 나가며,
1394로 설정하면 '채널'에서 지정한 채널번호로 디버그 정보가 제공된다.

공유하기 버튼

 
 

WinDbg를 이용한 VMWare의 가상머신 Kernel debugging - VMWare 설정 비전공 개발자

근래 드라이버 쪽 일을 하면서 여러모로 WinDbg를 자주 사용하고 있다.
특히나 그 중에서 드라이버를 위한 디버깅이라 하면 kernel mode에서의 디버깅을 예로 들 수 있을 것이다.
기존에 드라이버를 개발하던 사람들은 대개가 Serial port를 활용하여 시리얼 통신으로 Debugger와 Debuggee의 2대 이상의 PC를 사용하여 동작을 확인하곤 했다.
대개의 Visual Studio 개발자들은 작성하던 프로그램들에 대해 Visual Studio를 바로 디버거로 활용하여 디버깅 작업을 하는 경우가 가장 흔한 경우이다.

이 경우 대개가 실행 가능한 프로세스에 대해 소스 코드 기반으로 직접 실행해 나가는 방법이기 때문에 가장 빠르게 오류 사항들을 잡아낼 수 있고, 또한 수정한 내용을 바로 확인할 수 있다는 강점이 있다.
여기에 붙여서 실행 중인 프로세스에 대한 디버깅도 가능하다.
Visual studio의 디버그 관련 메뉴에서는 직접 대상 프로세스를 선택하여 디버깅을 시도할 수 있도록 되어 있다.
하지만, 이 방법에는 고가의 Visual studio라는 개발툴이 설치되어 있어야 한다는 점이 단점으로 남는다.

드라이버 개발을 시작하면서부터는 확실히 이런 부담이 줄어 들었다.
메모장같은 일반 편집툴을 사용하여 소스 코드를 작성해도 전혀 문제가 되지 않는다.
여기에 MS에서 무상으로 제공하고 있는 WDK라는 개발 환경을 설치하면 메모장으로 작성한 소스를 바로 자신이 원하는 최적의 환경에서 컴파일할 수 있다.
여기에 더해서 WDK를 설치하면 WinDbg가 설치되어 디버깅을 위한 쉬운 접근이 가능하게 한다.

Debugging 이라는 분야는 생각보다 광범위하다.
일반적으로는 OS 환경에서 이뤄지는 거의 모든 문제에 대한 접근의 방법으로 사용되고 있는 방법이다.
윈도우에서 정말 보기 싫은 BSOD 화면을 보게 된다거나, 새로 설치한 프로그램이 실행만 되면 메시지박스를 토해낸다.
그리고, 언제부터인지 모르게 시스템이 현격히 느려진 것을 체감하고 있다면 그것은 내가 어느샌가 애완용으로 벌레를 키우고 있다는 말이 될 것이다.
물론 많은 선험자들이 이 모든 사항을 벌레로 취급하지는 않지만, 분명 그 선을 구분하는 데에는 모호한 점이 많다.

여튼 그래서 오늘은 특별히 VMWare를 사용한 Virtual OS 상에서의 디버깅을 확인하기 위한 방법을 살펴보고자 한다.

일단 Host와 VMWare 상의 디버그를 위한 통신을 위해서는 Piping이 사용된다.
Piping이란 OS 상에 가상의 포트를 정해두고, 이 포트의 이름을 아는 프로세스들 간에 이루어지는 통신의 한 방법으로 이해하는 것이 좋다.
물론 이것으로 다 설명할 수 있을 만큼 간단한 기술은 아니지만, 의외로 사용하기 간편한 IPC 중의 하나임에는 분명하다.
그럼 일단 통신을 위한 기반으로 VMWare 상에서의 설정을 진행해 보자.

일단 VMWare에서 대상이 되는 OS를 선택한다.

여기서 좌측 메뉴에서 해당 OS를 Right Click하여 나타나는 Submenu에서 Settings를 선택하면 Virtual Machine Settings라는 화면이 나타난다.
이 화면에 보이는 "Serial Port 2"는 지정된 OS에서 COM 2 포트를 사용해 디버그 정보를 제공한다는 말이다.
기본적으로 OS를 설치한 후에는 이 항목이 나오지 않지만, 이 화면에서 해당 포트를 추가할 수 있다.

화면의 밑부분에서 "Add..."버튼을 선택한다.
나타난 화면에서 Serial Port를 선택하고 Next를 선택한다.

변경된 화면에서 Output to named pipe 항목을 선택하고 Next를 선택한다.
이 화면에서 파이프의 명칭을 지정할 수 있다.

\\.\pipe\xcom

이것은 현재 PC의 pipe로서 xcom이라는 포트를 지정하여 열겠다는 의미이다.

This end is the server.

이것은  현재 이 pipe를 통한 한쪽 끝 - 디버그 정보를 수집한 측이 VMWare가 설치된 PC라는 의미이다.

This end is the client.

이것은 그 반대되는 개념이겠지?

The other end is a virtual machine.
The other end is an application.

이 것은 구분이 모호하다.
문자 그대로 받아 들인다면 VMWare 자체가 될 수도 있고, application이 될 수도 있다는 의미인데...
뭔 생각으로 이렇게 애매하게 만들었나 몰라.
여튼 현재 테스트한 바로는 두번째 항목으로 선택해야 디버그 정보를 받을 수 있다.

그 밑의 Device status에 있는 Connect at power on 항목은 반드시 체크해 준다.
이건 말 그대로 해당 OS를 기동시키면 지정한 파이프로 자동으로 연결된다는 의미로 보면된다.
Finish를 선택한다.
모두 설정하고 나면 이전과 같은 화면을 확인할 수 있다.
이 화면에서 설정된 내용을 확인하고, 반드시 'I/O mode'의 'Yield CPU on poll'부분이 체크된 여부를 확인해준다.
반드시 체크하라는 말이다.

이 화면의 상단에서 Options 항목을 선택한다.
좌측 항목 중 Advanced 항목을 선택하면 우측의 항목을 확인할 수 있다.

가장 상단의 'Process priorities'에서 'Input grabbed'와 'Input ungrabbed' 항목을 Normal로 설정한 것을 확인할 수 있다.
이 항목은 원래 Default로 설정된 항목인데, Default와 Normal의 구분이 애매하여 Normal로 설정하였다.

또한, Settings에서는 'Gather debugging information' 항목이 Full로 설정된 것을 볼 수 있다.
여기서 None으로 설정하면 debugging 정보가 안 생긴다는 의미겠지...

이 중에서 Process priorities의 항목을 변경할 경우 맞춰줘야 할 것이 하나가 더 있다.
OK를 눌러 화면을 닫고 상단의 Edit 메뉴에서 Preferences를 선택한다.

나타난 화면의 상위 탭에서 Priority를 선택한다.
항목을 보면 알 수 있듯이 Normal은 Normal로 맞춰주자...
이 부분에 대한 VMWare에서의 설명을 보자면 다음과 같다.

Select: VM > Settings > Options > Advanced
In addition to the Advanced Options settings, this tab displays the paths to the virtual machine’s configuration (.vmx) file and its log file. Although you cannot edit these paths, you can place your cursor inside the paths and use arrow keys to scroll to the end of a path.
Process priorities – (Windows hosts only) The Normal setting means that the processes within virtual machines contend equally for resources with all other processes running on the host.
To change the global setting for process priority, which applies to all virtual machines, see Priority Tab.

해석은 알아서들 하시길...

너무 길어지니 일단 접는다.

공유하기 버튼

 
 

85 - MITSUBISHI PAJERO 1/65 토미카

왠지는 모르지만, 지대로 오프로드인 듯한...
맘에 드는 녀석인데
국내에서도 간간히 보이더군...

공유하기 버튼

 
 

이글루의 글 등록 방법에 문제 있는 거 아냐? 한 소리

아침에 이글루를 확인하니 관리자가 묘한 덧글을 달아놨다.

뭐 도배처럼 보이지 않도록 밸리 발행 간격을 조정해 달라나?

이건 뭘까...
분명 글을 등록하다보면 밸리를 등록하게 되는데, 연속으로 글을 등록할 때가 종종 발생한다.
경험상 이런 경우 이글루에서는 5개까지의 연속된 새글의 밸리 발행이 허용되는 것을 확인했다.
이것을 넘어가면 뭐라더라....한 시간 안에 5개를 넘는 글을 게재할 경우 게재는 되지만, 발행은 안된다는 메시지를 본 것 같은데...
그럼 그것에 맞춰서 한 시간 안에만 발행하지 않으면 되는 거 아닌가?

여태껏 그런 식으로 글을 게재해도 아무런 얘기가 없더니, 갑자기 여기에 대해 태클을 거는 이유가 뭔지 모르겠다.
막으려면 아예 2개나 3개까지만 연속 게재가 된다고 단서를 달든가...
이건 뭐 연속 게재가 되게 해 놓고는 경고성 멘트를 덧글로 달아두다니...

거기에 더해 이전에는 사진 만 등록해도 뭐라 그러지 않더니, 얼마 전부터는 사진 만 게재하면 뭐 광고나 스포성이 어쩌고 하는 댓글을 달았더라...
뭐지?
이것도 전에는 안 그랬지 않나?
사람 간보는 것도 아니고, 이번 주에 글 올리면서 매일 댓글 달아둔 것들을 보면 짜증이 용솟음 친다.

관리자 바뀐건지, 이글루 글 등록하는 기준이 바뀐건지...
바뀐 건 바꼈다고 사전에 알려 주면 좋잖아?

그냥 뜬금없이 잘 되던 것도 이러면 안되요, 저러면 큰일나요 라니....
댓글 달린 것 보고 짜증나기로는 광고성 댓글 이후 처음이네 그려...

아...싫다 정말...
네이년으로 튈까보다...

공유하기 버튼

 
 

109 - MITSUBISHI TRITON 1/66 토미카

이것은 벤 스타일의 짐차이다.
우리 나라의 예를 들자면 물쏘와 같은 컨셉의 짐칸을 살린 벤이다.
하지만, 우리 나라에서는 이런 스타일의 차를 좀처럼 볼 수 없다.
이런 짐차를 탈 바엔 다들 용달 트럭을 이용하거나, 아예 놀러가기 위한 벤을 구할 것이기 때문이다.
미국같은 땅덩이가 넓은 나라에서는 종종 볼 수 있는 차량이지...

공유하기 버튼

 
 

72 - MITSUBISHI FUSO AERO STAR ECO HYBRID 1/141 토미카

이것은 버스다.
토미카에 있는 몇 가지 버스 중의 하나.
솔직히...일본에서 버스를 타보고 느꼈던 건, 확실히 우리나라의 버스들보다 훨씬 좋았다는 것...
쓸데없는 썰은 그만 풀자...

특이한 것은 이건 스티커가 들어 있다는...
원래 버스 디자인에 충실하기 위해 추가된 것으로 보이는데, 붙이고 보면 생각보다 잘 빠졌다는 느낌.

공유하기 버튼

 
 

1 2 3 4 5 6 7 8 9 10 다음



메모장