2009/04/06 Stack Corruption 2
2009/03/11 Mini Dump에서 managed callstack 복원 2009/03/02 골치 아픈 Memory Fragmentation 2009/02/25 Stack Overflow에 대한 Debugging 2009/02/17 Kernel Mode에서의 High CPU issue 0:003> kbL ChildEBP RetAddr Args to Child WARNING: Frame IP not in any known module. Following frames may be wrong. 0470fc54 1a43fa39 058dfe44 00000000 001db2c0 0x251fcf 0470fc70 1a41dfdb 00000001 0470fc94 1a406bd4 ttt!CBaseMy::Neutralize+0x35 0:003> r eax=050d2000 ebx=00000000 ecx=00251f18 edx=00000000 esi=001db2c0 edi=00000000 eip=00251fcf esp=0470fc58 ebp=0470fc70 iopl=0 nv up ei pl nz na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206 00251fcf 006022 add byte ptr [eax+22h],ah ds:0023:050d2022=?? 보면, 0x251fcf 가 Function Name은 아닐 것이다. Garbage 임에 틀림없다. 그리고 Register 를 보면, eax 는 잘못되었음을 보여주고 있다. 이러한 Callstack에서 문제가 발생한 원인을 찾기 위해서는 마지막에 정상적으로 수행된 Function에서 부터 Check 할 필요가 있다. Windbg에서 kb 명령어를 수행할 줄 알아도 그 의미를 알지 못하면 아무 소용이 없다. 자. 그럼 어디를 어떻게 봐야 할 까. 다시 한번 Callstack을 보자. 0:003> kbL ChildEBP RetAddr Args to Child WARNING: Frame IP not in any known module. Following frames may be wrong. 0470fc54 1a43fa39 058dfe44 00000000 001db2c0 0x251fcf 0470fc70 1a41dfdb 00000001 0470fc94 1a406bd4 ttt!CBaseMy::Neutralize+0x35 Callstack에서 보여주고 있는 것은 Neutralize Function 안에서 뭔가 임의의 Function이 Call되었고 그것의 Function Name은 알 수 없다는 것이다. 만일 적절한 Function Name을 Debugger가 알 수 있었다면, 0x251fcf 라는 값을 출력하진 않았을 것이다. 하지만, 또 한가지 알 수 있는 것은 뭔지 모르는 마지막 Function Call에 대한 return address는 0x1a43fa39 라는 것이다. 이것이 깨졌는지 Check 해 봐야 한다. 만일, 이것 역시 깨졌다면, 다시 raw stack을 점검해야 할 것이다. 그러므로, 일단, Neutralize Function의 Assembly code를 확인할 수 있다. 0:003> uf ttt!CBaseMy::Neutralize 664 1a41df5a 8bff mov edi,edi 664 1a41df5c 56 push esi 664 1a41df5d 8bf1 mov esi,ecx . . .<중략> 675 1a41df7d 8b460c mov eax,dword ptr [esi+0Ch] 675 1a41df80 3bc7 cmp eax,edi 675 1a41df82 0f85ab1a0200 jne ttt!CBaseMy::Neutralize +0x2f (1a43fa33) 677 1a43fa33 8b08 mov ecx,dword ptr [eax] 677 1a43fa35 50 push eax 677 1a43fa36 ff5108 call dword ptr [ecx+8] 678 1a43fa39 897e0c mov dword ptr [esi+0Ch],edi 결국, 1a43fa39는 Assembly code에서 Class의 member function call인 'call dword ptr[ecx+8]' 에 대한 Return Address이다. 다시 assembly code와 register 값을 살펴보자. 0:003> dd @ecx 00251f18 ???????? ???????? ???????? ???????? 00251f28 ???????? ???????? ???????? ???????? 00251f38 ???????? ???????? ???????? ???????? 00251f48 ???????? ???????? 0000ffff 00252c04 확실히 ecx 값은 잘못되었다. assembly code를 따라가보면, ecx값은 esi register의 임의의 Offset으로 부터 얻은 값이고, Function의 초입부에 ecx 를 통해서 esi 값을 전달 받는 것으로 봐서는 esi는 this pointer가 틀림없다. 그러므로, this pointer의 member class 또는 member 변수에 대한 member function을 호출하는 것에 문제가 있었기 때문에 stack이 깨진 것으로 추측할 수 있으며, member 변수(ecx register)가 garbage 값이 들어 온 이유에 대해서 Code review를 통해 verify 해야 할 것이다. CLR Exception은 일반적으로 1st Chance Exception이므로, 메모리 덤프를 수집 시에 CLR Exception에 대한 Checking이나 1st chance full user dump 로 설정하지 않으며, 일반적인 Operation에 따라서 mini dump로 수집된다. 이 경우에는 메모리 덤프로 부터 충분한 managed 정보를 얻기가 어렵다. 하지만, 몇 가지 tip을 통해 정보를 수집하는 것이 가능할 수 있다. 다음의 예를 보자. 다음은 mini dump상에서 !clrstack 을 입력한 결과이다. 보시다시피 정확한 method 정보를 출력해 주진 않는다. Child-SP RetAddr Call Site 000000000012c910 00000642803bb906 xxx.Common.dll!Unknown 000000000012c990 00000642803bb6dd xxx.Common.dll!Unknown 000000000012ca90 00000642803b0fb5 xxxex.exe!Unknown 000000000012cae0 000006428046070b xxxex.exe!Unknown 000000000012cbe0 00000642803bfeab xxxex.exe!Unknown 000000000012cc50 00000642803bfd9f xxxex.exe!Unknown 000000000012ccb0 000006427f67d4a2 xxxex.exe!Unknown 이를 Native Callstack에서 보면, 아래와 같다. 아래 노란색 블락은 상위의 Managed Callstack과 matching이 되는 정보이다. Child-SP RetAddr Call Site 00000000`0012c6b0 00000000`77d6e314 ntdll!KiRaiseUserExceptionDispatcher+0x3a 00000000`0012c780 00000642`7f67cbd7 kernel32!CloseHandle+0x5f 00000000`0012c7b0 00000642`803bbfee mscorwks!DoNDirectCall__PatchGetThreadCall+0x7b 00000000`0012c850 00000642`803bbccd ttt.IL_STUB(IntPtr)+0x3e 00000000`0012c910 00000642`803bb906 0x642`803bbccd 00000000`0012c990 00000642`803bb6dd 0x642`803bb906 00000000`0012ca90 00000642`803b0fb5 0x642`803bb6dd 00000000`0012cae0 00000642`8046070b 0x642`803b0fb5 00000000`0012cbe0 00000642`803bfeab 0x642`8046070b 00000000`0012cc50 00000642`803bfd9f 0x642`803bfeab 00000000`0012ccb0 00000642`7f67d4a2 0x642`803bfd9f 여기서 좀 더 확인할 수 있는 것은 MethodDesc 정보이다. 예를 들어, 0x642`803bbccd 가 MethodDesc의 Jitted Address이므로, 다음과 같은 몇몇 정보를 더 얻을 수 있다. 0:000> !ip2md 0x642`803bbccd MethodDesc: 000006428016d1e8 Method Name: xxx.Common.dll!Unknown Class: 00000642801b4f28 MethodTable: 000006428016d340 mdToken: 06000080 Module: 0000064280014320 IsJitted: yes m_CodeOrIL: 00000642803bbbe0 일단, 해당 Method의 정보는 Clrstack의 출력과 동일하게 잘 이해할 수 없는 정보이다. 정확한 Method 명을 알기 위해서 해당 assembly에 대한 Binary가 필요하다. Binary에 포함되어 있는 MetaData에 해당 Token 정보를 통해 Method를 확인하는 작업이 가능하다. 이는 ildasm.exe 라는 tool이 정보를 제공할 수 있다. 해당 Binary를 Ildasm에서 Open 하고, View Menu의 Meta Info의 show를 Click 하면, MetaData 정보를 얻을 수 있다. Find 를 통해서 해당 Token(06000080) 으로 확인해 보면 아래와 같이 해당 Method 이름이 Foo2Bar 임을 알 수 있다. Method #22 (06000080) ------------------------------------------------------- MethodName: Foo2Bar (06000080) Flags : [Private] [HideBySig] [ReuseSlot] (00000081) RVA : 0x00004e90 ImplFlags : [IL] [Managed] (00000000) CallCnvntn: [DEFAULT] hasThis ReturnType: Void 1 Arguments Argument #1: String 1 Parameters (1) ParamToken : (080000a8) Name : label flags: [none] (00000000) 이와 같은 방법을 통해서 mini dump에서 managed callstack을 복원할 수 있다. Application Debugging을 하다 보면, 간간히 들어오는 Issue가 OOM(Out Of Memory)에 대한 이슈인데, 이거 생각보다 골치가 아프다. 사실 이를 위해서 DebugDiag 나 UMDH 라는 훌륭한 Tool에 의지하며, Allocation pattern을 Check 하는 것은 debugging도 아니지만, 눈에 띄는 Allocation pattern이나 메모리상에 보유하는 있는 실제 Allocation(Committed) region이 그다지 높지 않은 데도 불구하고 OOM현상이 발생할 수 있다는 데에 어이없어 질 때가 있다. 그러면서 Application 개발자는 분명히 Allocation/release 를 엄격하게 지켰을 뿐인데... 라고 말하기도 한다. 이런 경우 무엇을 할 수 있을 까. 이는 Memory Fragmentation 현상으로 판단한다. 상당히 자잘한 Memory Alloc이 셀 수 없이 반복되면서 메모리 조각화가 발생하는 것이다. 간혹, 큰 메모리 덩어리가 할당/해제 되기도 하고 그러면서 메모리 조각화는 더 할당 받을 수 있는 free 영역이 있음에도 불구하고 !address를 Check해보면, 연속된 여분의 메모리 블럭이 존재하지 않아서 OOM을 유발하게끔 한다. 이러한 경우에 일차적으로 할 수 있는 것은 1) 어찌됐든 불필요한 작고 많은 memory 조각들이 Leak으로 존재하는 부분을 제거해야만 한다. 이는 DebugDiag의 Leak tracking을 통해서 Checking될 수 있다. DebugDiag에서 제공하는 Leak 분석에서 제공하는 Callstack 정보는 그 메모리 보유 Size가 크지 않더라도 Allocation Count를 Check하여 메모리상에 산재됨으로 인한 Fragmentation의 원인이 되진 않는 지 Check 해볼 수 있다. 2) 두 번째는 문서 http://support.microsoft.com/kb/315407 에서 제공하는 registry key, HeapDecommitFreeBlockThreshold 가 도움이 될 수 있다. 이는 레지스트리에 설정된 값 이상이 되는 경우가 아니면, 재사용을 위해 decommitted 되지 않는 상태로 유지하는 것이다. HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager HeapDecommitFreeBlockThreshold REG_DWORD - 0x00040000 (권고) 3)사실 2번째 방법이 Application을 위해서 얼마나 효율적인지 경험해보진 못했다. 아마도 Windows XP/2003에서 도입된 LFH (Low Fragmentation Heap)을 사용하도록 Application을 구성하는 것이 가장 효율적일지 모르겠다. 이는 문서 http://msdn.microsoft.com/en-us/library/aa366750.aspx 에도 언급되었지만, HeapSetInformation API를 이용해서 HeapCompatibilityInformation(0) 값을 parameter로 전달함으로써 Application Level에서 설정할 수 있다. http://msdn.microsoft.com/en-us/library/aa366705(VS.85).aspx 문서에는 다음과 같은 예제를 확인할 수 있다. 이는 Application에서 사용하는 모든 Heap을 LFH를 사용하도록 설정할 수 있다. HANDLE heaps[1025]; DWORD nheaps = GetProcessHeaps(1024, heaps); for (DWORD i = 0; i < nheaps; i++) { ULONG HeapFragValue = 2; HeapSetInformation(heaps[i], HeapCompatibilityInformation, &HeapFragValue, sizeof(HeapFragValue)); } 이러한 것들이 Memory Fragmentation을 위해 도움이 될 듯 하다. Program의 수행에서 CPU의 연산을 위해 Stack을 사용한다는 점에서 Stack의 Size가 제한되어 있다는 것은 개발자라면 항상 염두에 두고 있을 것이다. 그러므로, 무한 재귀 호출되는 Function이나 과도하게 Stack 상에다 Allocation한 후(일반적으로 Local variables나 parameters는 Stack에 저장된다.) Exception이나 비정상적인 operation으로 인하여 Stack이 unwind 되지 않는 다면, 예기치 않은 Stack overflow를 맞을 수 있다. 다음의 Stack overflow dump case를 예로 든다. 일단, 메모리덤프를 Open 하면, 대번 Stack Overflow임을 알 수 있다. (24f8.f64): Stack overflow - code c00000fd (first/second chance not available) eax=000900a0 ebx=00000000 ecx=08ac2bf0 edx=0e010026 esi=0e010024 edi=08aff134 eip=033f9577 esp=08afebe8 ebp=08afec1c iopl=0 nv up ei pl nz na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010206 *** WARNING: Unable to verify checksum for ttt.dll *** ERROR: Symbol file could not be found. Defaulted to export symbols for ttt.dll - ttt!DllUnregisterServer+0x33a4: 033f9577 8501 test dword ptr [ecx],eax ds:0023:08ac2bf0=00000000 Stack Size는 다음과 같이 Check 할 수 있다. 0:066> !teb TEB at 7fefd000 ExceptionList: 08afec10 StackBase: 08b00000 StackLimit: 08ac1000 SubSystemTib: 00000000 FiberData: 00001e00 ArbitraryUserPointer: 00000000 Self: 7fefd000 EnvironmentPointer: 00000000 ClientId: 000024f8 . 00000f64 RpcHandle: 00000000 Tls Storage: 7fefd02c PEB Address: 7ffd8000 LastErrorValue: 0 LastStatusValue: c0150008 Count Owned Locks: 0 HardErrorMode: 0 0:066> ? 08b00000 - 08ac1000 Evaluate expression: 258048 = 0003f000 실제, Max Allocation할 수 있는 Stack Size를 확인해 보면, 대략 256k임을 확인할 수 있다. 이는 아래와 같은 TEB Structure의 DeallocationStack 값을 확인하면 되며, 이는 http://msdn.microsoft.com/en-us/library/cc267849.aspx 문서를 참조하거나 debugging tools for windows help에서 참조할 수 있다. typedef struct _TEB { 0:066> dd 7fefd000+e0c L1 7fefde0c 08ac0000 0:066> ? 08b00000 - 08ac0000 Evaluate expression: 262144 = 00040000 <---- 256k Max allocation size 그러므로, 현재 사용하는 Stack은 3f000 으로 Max Stack Size, 40000 에 도달하고 있음을 확인할 수 있다. 원인을 찾기 위해선 무엇보다도 먼저 재귀 호출이 있는 지 확인이 필요한데, 이는 Callstack을 확인하는 작업이 필요할 듯 하다. 일단, 해당 case에서는 안타깝게도 해당 모듈에 대한 Symbol이 정확하지 않아서 확인이 불가능했으며, 정확한 symbol이 존재한다면 아래의 Callstack에서 확인된 Function을 Check해야 한다. 0:066> kbL ChildEBP RetAddr Args to Child WARNING: Stack unwind information not available. Following frames may be wrong. 08afec1c 033f4de9 0df4002c 08aff0fc 0000fde9 ttt!DllUnregisterServer+0x33a4 <-- Symbol이 맞지 않아서 이렇게 보이는 것임. 08aff118 033f40c8 0b056198 0b056228 00000000 ttt+0x4de9 08aff170 762e31eb 0b055f20 08aff124 08aff120 ttt+0x40c8 08aff198 7635184f 033f3e71 08aff3a0 00000005 rpcrt4!Invoke+0x2a 08aff5c4 7634fc79 09dae6f8 06afcbc8 018be228 rpcrt4!NdrStubCall2+0x27b 08aff614 760527f7 09dae6f8 018be228 06afcbc8 rpcrt4!CStdStubBuffer_Invoke+0xffffdd13 08aff638 76909759 09dae6f8 018be228 06afcbc8 oleaut32!CUnivStubWrapper::Invoke+0xc7 . . . 만일, Function의 무한 재귀호출에 의해서 Stack Overflow가 발생한 것이 아니라면, Stack에 allocation 패턴도 Check해야 한다. 0:066> dds esp 08afebe8 75f751ea kernel32!lstrlenW+0x56 08afebec 033f18c5 ttt+0x18c5 08afebf0 08aff134 08afebf4 0b0584a8 08afebf8 08aff148 08afebfc 01e62f00 08afec00 0b0584a8 08afec04 000cc09e 08afec08 08afec24 08afec0c 033f188b ttt+0x188b 08afec10 08aff10c 08afec14 0340c9f0 ttt!DllUnregisterServer+0x1681d 08afec18 00000000 08afec1c 08aff118 08afec20 033f4de9 ttt+0x4de9 08afec24 0df4002c 08afec28 08aff0fc 08afec2c 0000fde9 08afec30 0000007b 08afec34 0b055f20 08afec38 00000005 08afec3c 00000000 Callstack에서 문제가 발생한 시점이전의 최종 Return Address가 33f4de9 인데, 그 이후에 Stack address(상위 raw stack에서의 회색 블럭)는 local variables 또는 parameters 등으로 사용되므로, 이들 중 Allocation 패턴이 overflow를 의심하게 하는 것이 있는 지 Check가 필요하다. 0:066> dc 0b0584a8 0b0584a8 0340e448 0b0500c4 00000000 00000000 H.@............. 0b0584b8 ee7ff470 08002142 00000001 0000261c p...B!.......&.. 0b0584c8 0000261c 000a000d 006f0043 0074006e .&......C.o.n.t. 0b0584d8 006e0065 002d0074 00790054 00650070 e.n.t.-.T.y.p.e. 0b0584e8 0020003a 006d0069 00670061 002f0065 :. .i.m.a.g.e./. 0b0584f8 00690067 003b0066 006e0020 006d0061 g.i.f.;. .n.a.m. 0b058508 003d0065 00630022 0069006c 005f0070 e.=.".c.l.i.p._. 0b058518 006d0069 00670061 00300065 00330030 i.m.a.g.e.0.0.3. 0:066> du 0b0584c8+8 0b0584d0 "Content-Type: image/gif; name="c" 0b058510 "lip_image003.gif"..Content-ID: <" 0b058550 "20090223.44575.001@tagfree.com>." 0b058590 ".Content-Transfer-Encoding: base" 0b0585d0 "64..Content-Location: file:///C:" 0b058610 "/DOCUME~1/jkim/LOCALS~1/Temp/mso" 0b058650 "html1/01/clip_image003.gif....R0" 0b058690 "lGODlhKwHTAHcAMSH+GlNvZnR3YXJlOi" 0b0586d0 "BNaWNyb3NvZnQgT2ZmaWNlACH5BAEAAA" 0b058710 "AALBUACQAA..AcMAhgAAAAAAABoaTSYm" 0b058750 "czMzmWaAgIAzAICAAJaWlpm/v5/P/57O" 0b058790 "/53O/5zN/5vN/5rM/5nM/79N..AL/d/7" 0b0587d0 "7d/7zc/7vb/7ra/7na/73c/7jZ/7fZ/7" 0b058810 "bY/7XY/7TY/7PX/7LX/7DW/6/W/7TX/6" 0b058850 "/V/67V../63U/6vT/6rT/6nT/6jT/6zU" 0b058890 "/6vU/6fS/6bS/6bR/6XR/6TQ/6PQ/6LQ" 0b0588d0 "/6HP/6DP/8Tf/8Le/8He../8De/8Pf/8" 0b058910 "z//9/u/97t/93s/9zr/9vr/9rq/9nq/9" 0b058950 "jq/9rr/9bp/9Xo/9Tn/9fp/9Lm/9Hm/8" 0b058990 "/l../9Pn/87l/83k/9Dm/8zj/8vj/8ni" 0b0589d0 "/8ji/8fh/8bh/8Xg//9mAP//AP//zP7/" 0b058a10 "//7+//3+//z9//v9..//v8//r8//n8//" 0b058a50 "j7//f7//b6//X5//T4//P4//L4//H3//" . . .<중략> 그러므로, 만일, 상위에서 보여주는 몇몇의 data가 과도한 Allocation이나 Allocation 패턴에 의해서 문제 상황을 가져오지는 않는 지는 최종적으로 Source Code를 통해서 확인해봐야 하며, 그러기 위해서는 정확한 Symbol의 match가 중요함을 잊지 말아야 한다. High CPU 문제는 Memory Leak 문제와 함께 troubleshooting 하기가 껄끄러운 문제이다. 대부분이 적절한 Tool의 도움을 받아야만 풀 수 있다는 것도 유사하다. 일반적으로 High CPU Issue는 Performance Monitor의 도움을 받아 특정 Application이 CPU를 소비하고 있다는 것을 확인할 수 있으며, High CPU 기간 동안 메모리 덤프를 수집하여 누가 지속적으로 CPU time을 잡아 먹고 있는 지를 확인하는 작업이 일반적이었다. 이러한 작업은 kernel mode에서 CPU를 소비하는 경우에 어려움이 있었다. 문제 시점에 kernel dump를 수집한다고 해도 이는 snapshot 이기 때문에 High CPU가 지속되는 과정을 단정 짓기 어려웠다. 그러므로, Kernel trace를 위한 CPU Sampler 와 같은 Tool이 필요한데, Xperf 라는 Tool은 이러한 CPU Sampler를 제공하는 유용한 Tool이다. Perfmon 이나 필요에 의한 Kernel Dump 및 User Dump와 함께 Check 할 경우에 문제를 보다 정확하게 좁힐 수 있다. 특히, High CPU rates의 Callstack을 제공하는 데, 이는 XP나 Windows 2003과 같은 Vista 이전 OS에서는 제공되지 않아서 안타깝다. Xperf 는 다음의 Link에서 download 받을 수 있다. http://msdn.microsoft.com/en-us/performance/cc752957.aspx Download 받으면, trace를 capture 하는 xperf.exe와 View할 수 있는 xperfview.exe를 제공한다. 먼저, trace를 수집하는 방법은 여러 가지가 존재하지만, 다음과 같이 간단하게 수집할 수 있다. Vista에서 Administrator 권한 하에서 작업을 한다. xperf –on DiagEasy 그리고, trace 수집이 요구되는 일정기간이 지난 후에 다음과 같이 trace를 종료하여 자료를 수집한다. xperf –d ktlog.etl 이와 같이 High CPU 상태에서의 Kernel trace log를 수집할 수 있다. 그리고, 해당 etl file은 xperfview.exe에서 open하여 확인한다. 아래의 데이터는 CPU sampling by process의 화면이다.
메뉴에 Graphs 항목이 존재하는 데, CPU Sampling 외에도 CPU나 Disk utilization by process 또는 thread, interrupt service routine 이나 deferred procedure call, hard faults 그리고, Disk IO의 detail한 정보들을 graph로 확인할 수 있다. 특히, CPU sampling은 CPU consuming rates에 따른 Callstack 정보를 확인할 수 있는 데, 해당 정보를 얻기 위해서는 Symbol Files을 맞춰 줄 필요가 있다. Trace 메뉴에 configure symbol paths를 Click 하면, _NT_SYMBOL_PATH 입력 창이 있는 데, 여기에 Microsoft public symbol path(srv*c:\pubsymbols*http://msdl.microsoft.com/download/symbols )를 입력한다. 그리고, trace 메뉴의 load symbols를 Check 하면, CPU sampling 시에 Symbols이 load 될 것이다. 그리고, 마우스를 이용하여 상위 그림에서처럼 임의의 view를 select 할 수 있고 또는 전체 view를 select 한 후에 simple summary table을 click 하면 CPU sampling 정보를 얻을 수 있다.
반드시 load symbols가 check되어 있는 것을 확인해야 한다. 그래야만 아래 sample data처럼, module에 대한 function 정보를 추출할 수 있다.
CPU sampling data를 보면, 직관적으로 %Weight정보를 통해서 Process별 CPU rates을 확인할 수 있으며, Module 정보에 따른 어떤 Function이 CPU를 많이 소비하고 있는 지 여부를 확인할 수 있으므로, 해당 정보를 통해 High CPU Issue를 보다 쉽게 접근할 수 있다. 보다 구체적인 Callstack 정보는 summary table을 Click 하면 다음과 같이 stack 정보를 알 수 있다. stack 정보는 Callstack 정보이므로, 오른쪽 마우스를 Click하여 해당 stack을 Copy하여 확인하는 것이 필요할 수 있다.
|
카테고리
이글루링크
최근 등록된 덧글
그러세요
by 강세윤 at 12/15 오늘 많이 헤매다..알게 .. by youna at 12/14 글 잘 읽었습니다 . 전 .. by 위시 at 11/26 어렷다 by klhk at 11/09 dhjjgbem by kl at 11/09 17번부터 어떻게 접는지.. by tykim0131 at 10/28 ATL이나 MFC를 이용하.. by 김명신 at 09/24 복원되었군요.. 제 RSS.. by 강세윤 at 09/24 허걱, 하고 있는 것으로.. by 강세윤 at 09/15 RSS 주소 서비스는 안 .. by 정성태 at 09/15 이글루 파인더
| |||||