프로가 되자.

post search result

디버깅와 관련된 글 2개를 찾았습니다.

  1. 2009/06/11 Objective-C gdb로 디버깅 하기
  2. 2008/04/09 IsDebuggerPresent() 구현하기

Objective-C gdb로 디버깅 하기

Objective-C를 debug할 때 다음과 같이 break point(이하 bp)를 지정할 수 있습니다.

b -[UIViewController setInterfaceOrientation:]

이렇게 지정한 뒤 실행하면 다음과 같이 중지됩니다.

Pending breakpoint 1 - "-[UIViewController setInterfaceOrientation:]" resolved
(gdb)


bt 명령을 이용하면 call stack를 볼 수 있습니다.

#0  0x3097263a in -[UIViewController setInterfaceOrientation:] ()
#1  0x30978386 in -[UIViewController viewDidMoveToWindow:shouldAppearOrDisappear:] ()
#2  0x309221fb in -[UIView(Internal) _didMoveFromWindow:toWindow:] ()
#3  0x30920883 in -[UIView(Hierarchy) _postMovedFromSuperview:] ()
#4  0x3092079e in -[UIView(Internal) _addSubview:positioned:relativeTo:] ()
#5  0x3091a113 in -[UIView(Hierarchy) addSubview:] ()
.........
(gdb)


#0은 현재 bp 된 위치를 나타내며, bp를 걸었던 UIViewController setInterfaceOrientation: 에서 멈춰있는 것을 볼 수 있습니다.
보통 call stack은 main()부터 시작하며, #<번호> 부분으로 이동하기 위해서는 frame 명령어를 사용합니다. 예를 들어 "#1 -[UIViewController viewDidMoveToWindow:shouldAppearOrDisappear:]" 부분으로 이동하기 위해서는 "frame 1"과 같이 입력합니다. 또는, 현재 위치를 기준으로 +1 되어 있으므로 "up 1"를 입력하여도 됩니다.

"frame 1"로 입력하였을 경우 다음과 같이 현재 위치가 변경됩니다.

(gdb) frame 1
#1  0x30978386 in -[UIViewController viewDidMoveToWindow:shouldAppearOrDisappear:] ()
(gdb)


이 상태에서 인자를 보고 싶을 때 일반 c application은 인자들이 목록에 나와서 display 명령어로 볼 수 있습니다.
이 때 주의할 점은 $ebp레지스터를 이용하기 때문에, 다른 frame의 값은 볼 수 없다는 것 입니다.

(gdb) frame 0
#0  0x3097263a in -[UIViewController setInterfaceOrientation:] ()
(gdb) display *(int *)($ebp + 16)
1: *(int *) ($ebp + 16) = 1
(gdb)


위와 같이 0 frame으로 변경한 뒤, setInterfaceOrientation:의 첫번째 인자를 보면 1로 표시됩니다.
$ebp는 함수 시작 시 설정되는($esp가 저장되어) stack의 시작 주소 이기 때문에 (int *)로 캐스팅 한 뒤 역참조 연산자인 *를 붙여서 정수 값을 출력 하였습니다.
 
일반 c 프로그램을 디버깅 할 때는 display가 유용하지만 Objective-C에서는 다른 방법으로 봐야 합니다. (display로 봐도 되긴 합니다)

(gdb) po _rootView
<UIView: 0xd1a650; frame = (0 0; 320 480); layer = <CALayer: 0xd1a680>>
(gdb)


display를 이용하면 다음과 같이 나옵니다.
(gdb) display _rootView
1: self->_rootView = (UIView *) 0xd1a650
(gdb) display *_rootView
2: *self->_rootView = {
  <UIResponder> = {
    <NSObject> = {
      isa = 0x31a5c620
    }, <No data fields>}, 
  members of UIView: 
  _layer = 0xd1a680, 
  _tapInfo = 0x0, 
  _gestureInfo = 0x0, 
  _gestureRecognizers = 0x0, 
  _charge = 0, 
  _tag = 0, 
  _viewFlags = {
    userInteractionDisabled = 0, 
    implementsDrawRect = 0, 
    implementsDidScroll = 0, 
    implementsMouseTracking = 0, 
    hasBackgroundColor = 0, 
    isOpaque = 1, 
    becomeFirstResponderWhenCapable = 0, 
    interceptMouseEvent = 0, 
    deallocating = 0, 
    debugFlash = 0, 
    debugSkippedSetNeedsDisplay = 0, 
    debugScheduledDisplayIsRequired = 0, 
    isInAWindow = 0, 
    isAncestorOfFirstResponder = 0, 
    dontAutoresizeSubviews = 0, 
    autoresizeMask = 0, 
    patternBackground = 0, 
    fixedBackgroundPattern = 0, 
    dontAnimate = 0, 
    superLayerIsView = 0, 
    layerKitPatternDrawing = 0, 
    multipleTouchEnabled = 0, 
    exclusiveTouch = 0, 
    hasViewController = 0, 
    needsDidAppearOrDisappear = 0, 
    gesturesEnabled = 1, 
    capturesDescendantTouches = 1, 
    deliversTouchesForGesturesToSuperview = 1, 
    chargeEnabled = 0, 
    skipsSubviewEnumeration = 0
  }
}
(gdb)


무엇인가 po가 더 간단해보이죠??? (장, 단점이 있습니다)

디버깅을 Xcode가 다 해준다지만.. 그래도 gdb 정도는 쓸 수 있어야겠죠???
크리에이티브 커먼즈 라이센스
Creative Commons License
2009/06/11 12:55 2009/06/11 12:55

top

About this post

이 글에는 아직 트랙백이 없고, 아직 댓글이 없고, , , 태그가 달려있으며,
2009/06/11 12:55에 작성되었습니다.

IsDebuggerPresent() 구현하기

Windows API 중에 IsDebuggerPresent()가 있습니다. 무엇인고 하니.. 만약 현재 프로세스에 debugger가 물려있는지의 여부(즉 내가 debugee 상태라면)를 알려줍니다. 프로그램 크랙할 때
BOOL bIsDebugger;
bIsDebugger = IsDebuggerPresent();
if(bIsDebugger == TRUE)
{
    // error!
}
와 같은 루틴을 찾아서 if문을 patch하는 방식을 주로 이용하는데요.. 이것을 직접 구현하는 방법을 알아보도록 하겠습니다. Windows에서는 Thread Environment Block라는 블록이 있습니다. 말 그대로 Thread에 관련된 환경값이 있는데요, 이 중 Thread가 속한 Process Environment Block를 구할 수 있는 field가 있습니다. TEB는 FS:[0x18]로 구하고, PEB는 아래에 나오는 구조체의 필드로부터 메모리 주소값을 얻어와서 구합니다. 왜 FS:[0x18]이냐... ring3에서는 fs레지스터가 teb를 가리키고 있게 구성 되어 있다고 합니다. 그렇다면 우선 Winternl.h에 선언되어 있는 PEB와 TEB의 구조를 보겠습니다. (VS 2003입니다) 재미있는건 PEB를 IsDebuggerPresent(), CheckRemoteDebuggerPresent() 대신 BeingDebugged 필드를 이용하여도 된다고 하네요.
//
// Instead of using the BeingDebugged field, use the Win32 APIs
//     IsDebuggerPresent, CheckRemoteDebuggerPresent
// Instead of using the SessionId field, use the Win32 APIs
//     GetCurrentProcessId and ProcessIdToSessionId
// Sample x86 assembly code that gets the SessionId (subject to change
//     between Windows releases, use the Win32 APIs to make your application
//     resilient to changes)
//     mov     eax,fs:[00000018]
//     mov     eax,[eax+0x30]
//     mov     eax,[eax+0x1d4]
//
typedef struct _PEB {
    BYTE Reserved1[2];
    BYTE BeingDebugged;
    BYTE Reserved2[229];
    PVOID Reserved3[59];
    ULONG SessionId;
} PEB, *PPEB;

//
// Instead of using the Tls fields, use the Win32 TLS APIs
//     TlsAlloc, TlsGetValue, TlsSetValue, TlsFree
//
// Instead of using the ReservedForOle field, use the COM API
//     CoGetContextToken
//
typedef struct _TEB {
    BYTE Reserved1[1952];
    PVOID Reserved2[412];
    PVOID TlsSlots[64];
    BYTE Reserved3[8];
    PVOID Reserved4[26];
    PVOID ReservedForOle;  // Windows 2000 only
    PVOID Reserved5[4];
    PVOID TlsExpansionSlots;
} TEB;
typedef TEB *PTEB;
역시 MS......... PEB와 TEB의 구조를 다 Reserved로 바꿔놓았네요. 아래는 reserved를 없애고 실제 데이터가 의미하는 바를 나타낸 구조체 입니다.
typedef struct _CLIENT_ID
{
	HANDLE UniqueProcess;
	HANDLE UniqueThread;
} CLIENT_ID, *PCLIENT_ID;

typedef struct _RTL_USER_PROCESS_PARAMETERS
{
	BYTE Reserved1[16];
	PVOID Reserved2[10];
	UNICODE_STRING ImagePathName;
	UNICODE_STRING CommandLine;
} RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS;

typedef struct _PEB_LDR_DATA
{
	BYTE Reserved1[8];
	PVOID Reserved2[3];
	LIST_ENTRY InMemoryOrderModuleList;
} PEB_LDR_DATA, *PPEB_LDR_DATA;

typedef struct _PEB_FREE_BLOCK
{
	_PEB_FREE_BLOCK *Next;
	ULONG Size;
} PEB_FREE_BLOCK, *PPEB_FREE_BLOCK;

typedef void (* PPEBLOCKROUTINE)(PVOID PebLock);
typedef void **PPVOID;


typedef struct _PEB {
	BOOLEAN InheritedAddressSpace;
	BOOLEAN ReadImageFileExecOptions;
	BOOLEAN BeingDebugged;
	BOOLEAN Spare;
	HANDLE Mutant;
	PVOID ImageBaseAddress;
	PPEB_LDR_DATA LoaderData;
	PRTL_USER_PROCESS_PARAMETERS ProcessParameters;
	PVOID SubSystemData;
	PVOID ProcessHeap;
	PVOID FastPebLock;
	PPEBLOCKROUTINE FastPebLockRoutine;
	PPEBLOCKROUTINE FastPebUnlockRoutine;
	ULONG EnvironmentUpdateCount;
	PPVOID KernelCallbackTable;
	PVOID EventLogSection;
	PVOID EventLog;
	PPEB_FREE_BLOCK FreeList;
	ULONG TlsExpansionCounter;
	PVOID TlsBitmap;
	ULONG TlsBitmapBits[0x2];
	PVOID ReadOnlySharedMemoryBase;
	PVOID ReadOnlySharedMemoryHeap;
	PPVOID ReadOnlyStaticServerData;
	PVOID AnsiCodePageData;
	PVOID OemCodePageData;
	PVOID UnicodeCaseTableData;
	ULONG NumberOfProcessors;
	ULONG NtGlobalFlag;
	BYTE Spare2[0x4];
	LARGE_INTEGER CriticalSectionTimeout;
	ULONG HeapSegmentReserve;
	ULONG HeapSegmentCommit;
	ULONG HeapDeCommitTotalFreeThreshold;
	ULONG HeapDeCommitFreeBlockThreshold;
	ULONG NumberOfHeaps;
	ULONG MaximumNumberOfHeaps;
	PPVOID *ProcessHeaps;
	PVOID GdiSharedHandleTable;
	PVOID ProcessStarterHelper;
	PVOID GdiDCAttributeList;
	PVOID LoaderLock;
	ULONG OSMajorVersion;
	ULONG OSMinorVersion;
	ULONG OSBuildNumber;
	ULONG OSPlatformId;
	ULONG ImageSubSystem;
	ULONG ImageSubSystemMajorVersion;
	ULONG ImageSubSystemMinorVersion;
	ULONG GdiHandleBuffer[0x22];
	ULONG PostProcessInitRoutine;
	ULONG TlsExpansionBitmap;
	BYTE TlsExpansionBitmapBits[0x80];
	ULONG SessionId;
} PEB, *PPEB;

typedef struct _TEB
{
	NT_TIB Tib;
	PVOID EnvironmentPointer;
	CLIENT_ID Cid;
	PVOID ActiveRpcInfo;
	PVOID ThreadLocalStoragePointer;
	PPEB Peb;
	ULONG LastErrorValue;
	ULONG CountOfOwnedCriticalSections;
	PVOID CsrClientThread;
	PVOID Win32ThreadInfo;
	ULONG Win32ClientInfo[0x1F];
	PVOID WOW32Reserved;
	ULONG CurrentLocale;
	ULONG FpSoftwareStatusRegister;
	PVOID SystemReserved1[0x36];
	PVOID Spare1;
	ULONG ExceptionCode;
	ULONG SpareBytes1[0x28];
	PVOID SystemReserved2[0xA];
	ULONG GdiRgn;
	ULONG GdiPen;
	ULONG GdiBrush;
	CLIENT_ID RealClientId;
	PVOID GdiCachedProcessHandle;
	ULONG GdiClientPID;
	ULONG GdiClientTID;
	PVOID GdiThreadLocaleInfo;
	PVOID UserReserved[5];
	PVOID GlDispatchTable[0x118];
	ULONG GlReserved1[0x1A];
	PVOID GlReserved2;
	PVOID GlSectionInfo;
	PVOID GlSection;
	PVOID GlTable;
	PVOID GlCurrentRC;
	PVOID GlContext;
	NTSTATUS LastStatusValue;
	UNICODE_STRING StaticUnicodeString;
	WCHAR StaticUnicodeBuffer[0x105];
	PVOID DeallocationStack;
	PVOID TlsSlots[0x40];
	LIST_ENTRY TlsLinks;
	PVOID Vdm;
	PVOID ReservedForNtRpc;
	PVOID DbgSsReserved[0x2];
	ULONG HardErrorDisabled;
	PVOID Instrumentation[0x10];
	PVOID WinSockData;
	ULONG GdiBatchCount;
	ULONG Spare2;
	ULONG Spare3;
	ULONG Spare4;
	PVOID ReservedForOle;
	ULONG WaitingOnLoaderLock;
	PVOID StackCommit;
	PVOID StackCommitMax;
	PVOID StackReserved;
} TEB, *PTEB;
필드들이 참 많죠; 이 중 Winternl.h에 등록되어 있는 것도 있다고 합니다만, platform sdk를 업그레이드 안해서인지 VS 2003에는 구조체들이 없더라구요. 어쨌건간에.. FS:[0x18]에 가보면 진짜로(?) TEB가 저장되어 있습니다. VC에서 TEB를 구하기 위해서는 다음과 같이 작성합니다
DWORD dwTEBAddress;

__asm
{
	push eax
	mov eax, fs:[0x18]
	mov dwTEBAddress, eax
	pop eax
}
이게 뭔말인지 모르신다면..
HANDLE hThread;
BOOL bRet;
DWORD dwTEBAddress;
CONTEXT context;
LDT_ENTRY des;

hThread = GetCurrentThread();

// segment 정보만 얻어옴
context.ContextFlags = CONTEXT_SEGMENTS;

bRet = GetThreadContext(hThread, &context);
if(bRet == FALSE)
{
	// 오류 처리
}

// FS descriptor entry계산
bRet = GetThreadSelectorEntry(hThread, context.SegFs, &des);

dwTEBAddress = ((des.HighWord.Bytes.BaseHi << 8 | des.HighWord.Bytes.BaseMid) << 16 | des.BaseLow);
요렇게 구하셔도 됩니다. 이렇게 구하셨다면.. 이제 TEB에서 PEB를 구해야 합니다.
TEB *pTEB;
PEB *pPEB;
BOOL bIsDebugger;

pTEB = (TEB *)dwTEBAddress;
pPEB = pTEB->Peb;

bIsDebugger = pPEB->BeingDebugged;
이제 bIsDebugger를 확인 하면 땡입니다. 위와 같이 하는게 귀찮으시다(?) 하면.. PEB의 offset을 직접 계산하셔도 됩니다. 즉..
BOOL bIsDebugger;

bIsDebugger = (char *)*((void **)((char *)dwTEBAddress + 0x30)) + 0x02;
에 접근 하셔도 됩니다. 이걸 이용해서 함수로 만들어 보자면..
DWORD __declspec(naked) MyIsDebuggerPresent()
{
	__asm
	{
		// ecx 백업
		push ecx
		// TEB 주소 계산
		mov eax, fs:[0x18]
		// PEB 주소 계산
		mov ecx, dword ptr [eax + 0x30]
		// PEB->BeingDebugged 값을 가져오기 전 eax 초기화
		xor eax, eax
		// PEB->BeingDebugged 필드 값을 al에 저장(al = 8bit)
		mov al, byte ptr [ecx + 0x02]
		// ecx 복구
		pop ecx
		// // return eax
		retn
	}
}
와 같이 되겠습니다. C 버전으로 보길 원하신다면..
DWORD MyIsDebuggerPresent()
{
	HANDLE hThread;
	BOOL bRet;
	DWORD dwTEBAddress;
	CONTEXT context;
	LDT_ENTRY des;
	TEB *pTEB;
	PEB *pPEB;

	hThread = GetCurrentThread();

	// segment 정보만 얻어옴
	context.ContextFlags = CONTEXT_SEGMENTS;

	bRet = GetThreadContext(hThread, &context);

	if(bRet == FALSE)
	{
		return 0;
	}

	// FS descriptor entry계산
	bRet = GetThreadSelectorEntry(hThread, context.SegFs, &des);

	dwTEBAddress = ((des.HighWord.Bytes.BaseHi << 8 | des.HighWord.Bytes.BaseMid) << 16 | des.BaseLow);

	pTEB = (TEB *)dwTEBAddress;
	pPEB = pTEB->Peb;

	return pPEB->BeingDebugged;
}
이게 되겠네요.
크리에이티브 커먼즈 라이센스
Creative Commons License
2008/04/09 22:15 2008/04/09 22:15

top

About this post

이 글에는 아직 트랙백이 없고, 아직 댓글이 없고, , , , , , , 태그가 달려있으며,
2008/04/09 22:15에 작성되었습니다.

◀ recent : [1] : previous ▶

blog information

프로가 되자.
BLOG main image
빗소리를 먹는 사람.
RSS 2.0Tattertools
최근 글 최근 댓글 최근 트랙백
태그 구름사이트 링크