Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 
(연관된 글이 2개 있습니다.)

ClrMD를 이용해 메모리 덤프 파일로부터 특정 인스턴스를 참조하고 있는 소유자 확인

지난 글에서,

windbg - 닷넷 응용 프로그램의 메모리 누수 분석
; https://www.sysnet.pe.kr/2/0/11808

문자열이 쌓이고 있는 경우를 살펴봤는데, 분석 시 약간 아쉬운 점이 있습니다. 그 덤프의 힙 상태를 다시 보면,

0:000> !dumpheap -stat
Statistics:
              MT    Count    TotalSize Class Name
00007ff90f456748        1           24 Elasticsearch.Net.ElasticsearchDefaultSerializer
00007ff90f192f78        1           24 System.Collections.Generic.ObjectEqualityComparer`1[[System.Runtime.Caching.MemoryCache, System.Runtime.Caching]]
00007ff90f143038        1           24 System.Collections.Generic.Dictionary`2+ValueCollection[[System.String, mscorlib],[OracleInternal.SelfTuning.OracleTuner+OracleTunerInput, Oracle.ManagedDataAccess]]
00007ff90f0120b8        1           24 System.Net.SSPISecureChannelType
00007ff90f011e78        1           24 System.Net.SSPIAuthType
...[생략]...                
00007ff90d3869b8    12753      5127112 System.Int32[]
00007ff90d4ba6e0      159     14555944 System.Int64[]
00007ff90d1aba28   148673     16952928 System.Object[]
00007ff90d1fe828    75889     20114243 System.Byte[]
00007ff90ebd41e0   376168     36112128 TestApi.Data.AddrInfo
00007ff90d1f46f0  2726523    208786640 System.String
000000b69ba94870    33952    274313352      Free
Total 4063087 objects
Fragmented blocks larger than 0.5 MB:
            Addr     Size      Followed by
000000b6a40f38b0   11.4MB 000000b6a4c4d630 System.Byte[]

System.String을 소유하고 있는 객체로 (사용자가 만든 타입인) TestApi.Dat.AddrInfo 인스턴스가 많은 걸로 쉽게 짐작을 했는데요. 이런 식으로 찾아낼 수 없는 경우도 더러 있습니다. 예를 들어, 다음의 소스 코드를 덤프 떠서 살펴보면 어떨까요?

using System;
using System.Collections.Generic;

namespace ConsoleApp1
{
    class Program
    {
        static List<string> _list = new List<string>();

        static void Main(string[] args)
        {
            CreateObjects();
            GC.Collect(2);
            GC.Collect(2);

            Console.WriteLine("Dump!");
            Console.ReadKey();
        }

        private static void CreateObjects()
        {
            for (int i = 0; i < 100000; i ++)
            {
                _list.Add(Guid.NewGuid().ToString());
            }
        }
    }
}

string에 대한 소유를 1개의 List 객체가 담당하고 있으므로 heap 상태를 봐도 그 소유자를 쉽게 확인할 수 없습니다.

0:000> !dumpheap -stat
Statistics:
      MT    Count    TotalSize Class Name
719025c4        1           12 System.Collections.Generic.GenericEqualityComparer`1[[System.String, mscorlib]]
71900414        1           12 System.Collections.Generic.ObjectEqualityComparer`1[[System.Type, mscorlib]]
718fde9c        1           16 System.Security.Policy.AssemblyEvidenceFactory
7190be18        1           20 System.IO.Stream+NullStream
71901888        1           20 Microsoft.Win32.SafeHandles.SafeFileHandle
718fdde8        1           20 Microsoft.Win32.SafeHandles.SafePEFileHandle
7190c3dc        1           24 System.IO.TextWriter+SyncTextWriter
71902298        1           24 System.Version
71900994        2           24 System.Int32
71461e00        1           24 System.Collections.Generic.List`1[[System.String, mscorlib]]
7190c2e4        1           28 System.IO.__ConsoleStream
7190c29c        1           28 Microsoft.Win32.Win32Native+InputRecord
...[생략]...
71900958        6          452 System.Int32[]
718fff54       22          616 System.RuntimeType
718ff54c        6          626 System.Char[]
7190316c        1          783 System.Byte[]
71901e50        3          924 System.Globalization.CultureData
718fef34        4        17604 System.Object[]
718ff698       19       524868 System.String[]
00f2b548       24       525636      Free
718feb40   100154      8604850 System.String
Total 100299 objects

물론, "!dumpheap -mt 718feb40" 명령어를 이용해 문자열 인스턴스를 나열할 수 있지만 windbg에서 이 명령어를 수행하면 10만 개가 넘는 인스턴스를 출력하느라 한 세월이 걸립니다. 게다가 그중에서 몇 개를 골라 "!gcroot"로 찾아볼 수 있겠지만 무작위로 선정하는 것이므로 운이 좋아야 List를 가리키는 객체를 알아낼 수 있습니다. 또한, 그 객체가 문자열을 보관하는 (Leak이 없는) 또 다른 List 객체일 수도 있다는 점을 감안하면 찾기가 그리 쉽지 않습니다.




이런 상황을 해결하려면, windbg에서 스크립트를 이용해 10만 개의 System.String에 대한 참조를 유지하고 있는 객체 리스트를 작성하는 것입니다. 대표적으로 pykd를 이용하면 될 텐데요,

windbg에서 python 스크립트 실행하는 방법 - pykd
; https://www.sysnet.pe.kr/2/0/11227

아쉽게도 실제로 수행해보면 출력 결과에 대해 일일이 sos.dll의 명령어로 다시 수행하는 방식이어서 전체 과정이 꽤나 긴 시간을 요구하게 됩니다. 그래서 좀 더 빠른 방법을 찾게 되는데, 바로 그 해답이 ClrMD입니다.

Microsoft.Diagnostics.Runtime

Install-Package Microsoft.Diagnostics.Runtime -Version 1.0.2 

아울러 이미 제공되고 있는 풍부한 예제 코드에 따라,

Tutorials - 1. Getting Started - A brief introduction to the API and how to create a CLRRuntime instance.
; https://github.com/Microsoft/clrmd/blob/master/Documentation/GettingStarted.md

Tutorials - 3. Walking objects on the GC heap, working with types in CLR MD.
; https://github.com/Microsoft/clrmd/blob/master/Documentation/WalkingTheHeap.md

이거저거 짜깁기하면 우리가 원하는 목적의 코드를 다음과 같이 간단하게 만들 수 있습니다.

using Microsoft.Diagnostics.Runtime;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace RefOwner
{
    class Program
    {
        static string _platformPostfix = (IntPtr.Size == 4) ? "32" : "64";

        static void Main(string[] args)
        {
            if (args.Length < 2)
            {
                Console.WriteLine("RefOwner" + _platformPostfix + " [/d] [dumppath] [typename | address]");
                Console.WriteLine("[Sample]");
                Console.WriteLine("\tRefOwner" + _platformPostfix + " c:\\temp\\test.dmp System.String");
                Console.WriteLine("\tRefOwner" + _platformPostfix + "  /d c:\\temp\\test.dmp System.String");
                return;
            }

            string filePath = (args.Length == 3) ? args[1] : args[0];
            if (File.Exists(filePath) == false)
            {
                Console.WriteLine("FILE-NOT-FOUND: " + filePath);
                return;
            }

            string basePath = Path.GetDirectoryName(filePath);
            string dmpPath = filePath;
            string dacPath = Path.Combine(basePath, "mscordacwks.dll");

            string typeNameOrAddress = (args.Length == 3) ? args[2] : args[1];

            bool detailed = args.Length == 3 && args[0] == "/d";

            using (DataTarget dataTarget = DataTarget.LoadCrashDump(dmpPath))
            {
                foreach (ClrInfo version in dataTarget.ClrVersions)
                {
                    Console.WriteLine("Found CLR Version: " + version.Version);

                    // This is the data needed to request the dac from the symbol server:
                    ModuleInfo dacInfo = version.DacInfo;
                    Console.WriteLine("Filesize:  {0:X}", dacInfo.FileSize);
                    Console.WriteLine("Timestamp: {0:X}", dacInfo.TimeStamp);
                    Console.WriteLine("Dac File:  {0}", dacInfo.FileName);

                    string dacLocation = version.LocalMatchingDac;
                    if (!string.IsNullOrEmpty(dacLocation))
                        Console.WriteLine("Local dac location: " + dacLocation);

                    Console.WriteLine();

                    ClrRuntime runtime;

                    if (File.Exists(dacPath) == false)
                    {
                        runtime = version.CreateRuntime();
                    }
                    else
                    {
                        runtime = version.CreateRuntime(dacPath);
                    }

                    // DumpHeapStat(runtime);
                    // Console.WriteLine();
                    DumpHeapRefHierachy(runtime, typeNameOrAddress, detailed, 0);
                }
            }
        }

        private static void DumpHeapRefHierachy(ClrRuntime runtime, string typeNameOrAddress, bool detailed, int depth)
        {
            HashSet<ulong> instances = GetObjectListByName(runtime, typeNameOrAddress);
            Console.WriteLine(typeNameOrAddress + ", # of instances: " + instances.Count);

            Dictionary<string, HeapObjectCounter> owners = GetObjectOwners(runtime, instances);
            List<ObjectHistogramItem> histogram = new List<ObjectHistogramItem>();

            foreach (var owner in owners)
            {
                ObjectHistogramItem item = new ObjectHistogramItem { Key = owner.Key, Counter = owner.Value };
                histogram.Add(item);
            }

            histogram.Sort();

            int count = 0;

            foreach (var item in histogram)
            {
                Console.WriteLine($"{item.Counter.Total} {item.Key}({item.Counter.OwnerCount})");

                if (detailed == true)
                {
                    foreach (var instance in item.Counter)
                    {
                        Console.WriteLine($"\t[{instance.Key:x}, {instance.Value}]");
                    }
                }

                count += item.Counter.Total;
            }

            Console.WriteLine("Total: " + count);
        }

        private static ulong GetFieldAddressValue(ClrRuntime runtime, ClrInstanceField field, ulong addr)
        {
            ulong fieldAddress = field.GetAddress(addr);
            return ReadMemory(runtime, fieldAddress);
        }

        private static ulong ReadMemory(ClrRuntime runtime, ulong address)
        {
            bool x86 = IntPtr.Size == 4;

            unsafe
            {
                byte[] refAddr = null;

                if (x86 == true)
                {
                    refAddr = new byte[4];
                }
                else
                {
                    refAddr = new byte[8];
                }

                runtime.Heap.ReadMemory(address, refAddr, 0, IntPtr.Size);

                if (x86 == true)
                {
                    return BitConverter.ToUInt32(refAddr, 0);
                }
                else
                {
                    return BitConverter.ToUInt64(refAddr, 0);
                }                    
            }
        }

        private static Dictionary<string, HeapObjectCounter> GetObjectOwners(ClrRuntime runtime, HashSet<ulong> instances)
        {
            Dictionary<string, HeapObjectCounter> dict = new Dictionary<string, HeapObjectCounter>();

            if (!runtime.Heap.CanWalkHeap)
            {
                Console.WriteLine("Cannot walk the heap!");
            }
            else
            {
                foreach (ClrSegment seg in runtime.Heap.Segments)
                {
                    for (ulong obj = seg.FirstObject; obj != 0; obj = seg.NextObject(obj))
                    {
                        ClrType type = runtime.Heap.GetObjectType(obj);

                        if (type == null || type.IsFree == true)
                        {
                            continue;
                        }

                        string typeName = type.ToString();

                        int gen = runtime.Heap.GetGeneration(obj);
                        if (gen != 2)
                        {
                            continue;
                        }

                        if (type.IsArray == true)
                        {
                            int arrayLength = type.GetArrayLength(obj);
                            for (int i = 0; i < arrayLength; i++)
                            {
                                ulong elemAddress = type.GetArrayElementAddress(obj, i);

                                ClrType elemType = type.ComponentType;
                                if (elemType.IsValueClass == false)
                                {
                                    ulong elemRef = ReadMemory(runtime, elemAddress);
                                    if (instances.Contains(elemRef) == true)
                                    {
                                        if (dict.ContainsKey(typeName) == true)
                                        {
                                            dict[typeName].AddCount(obj);
                                        }
                                        else
                                        {
                                            dict.Add(typeName, new HeapObjectCounter(obj));
                                        }
                                    }
                                }
                                else
                                {
                                    AddItem(type, typeName, obj);
                                }
                            }
                        }
                        else
                        {
                            AddItem(type, typeName, obj);
                        }
                    }
                }
            }

            return dict;

            void AddItem(ClrType type, string typeName, ulong objAddress)
            {
                foreach (ClrInstanceField field in type.Fields)
                {
                    ulong fieldAddress = GetFieldAddressValue(runtime, field, objAddress);

                    if (instances.Contains(fieldAddress) == true)
                    {
                        if (dict.ContainsKey(typeName) == true)
                        {
                            dict[typeName].AddCount(objAddress);
                        }
                        else
                        {
                            dict.Add(typeName, new HeapObjectCounter(objAddress));
                        }
                    }
                }
            }
        }

        private static HashSet<ulong> GetObjectListByName(ClrRuntime runtime, string typeNameOrAddress)
        {
            HashSet<ulong> list = new HashSet<ulong>();

            ulong result;
            if (UInt64.TryParse(typeNameOrAddress, System.Globalization.NumberStyles.AllowHexSpecifier, null, out result) == true)
            {
                list.Add(result);
                return list;
            }

            if (!runtime.Heap.CanWalkHeap)
            {
                Console.WriteLine("Cannot walk the heap!");
            }
            else
            {
                foreach (ClrSegment seg in runtime.Heap.Segments)
                {
                    for (ulong obj = seg.FirstObject; obj != 0; obj = seg.NextObject(obj))
                    {
                        ClrType type = runtime.Heap.GetObjectType(obj);

                        if (type == null)
                        {
                            continue;
                        }

                        int gen = runtime.Heap.GetGeneration(obj);
                        if (gen != 2)
                        {
                            continue;
                        }

                        if (type.ToString() != typeNameOrAddress)
                        {
                            continue;
                        }

                        list.Add(obj);
                    }
                }
            }

            return list;
        }

        private static void DumpHeapObject(ClrRuntime runtime)
        {
            if (!runtime.Heap.CanWalkHeap)
            {
                Console.WriteLine("Cannot walk the heap!");
            }
            else
            {
                foreach (ClrSegment seg in runtime.Heap.Segments)
                {
                    for (ulong obj = seg.FirstObject; obj != 0; obj = seg.NextObject(obj))
                    {
                        ClrType type = runtime.Heap.GetObjectType(obj);

                        if (type == null)
                        {
                            continue;
                        }

                        ulong size = type.GetSize(obj);
                        Console.WriteLine("{0,12:X} {1,8:n0} {2,1:n0} {3}", obj, size, seg.GetGeneration(obj), type.Name);
                    }
                }
            }
        }

        private static void DumpHeapStat(ClrRuntime runtime)
        {
            Console.WriteLine("{0,12} {1,12} {2,12} {3,12} {4,4} {5}", "Start", "End", "CommittedEnd", "ReservedEnd", "Heap", "Type");

            foreach (ClrSegment segment in runtime.Heap.Segments)
            {
                string type;
                if (segment.IsEphemeral)
                    type = "Ephemeral";
                else if (segment.IsLarge)
                    type = "Large";
                else
                    type = "Gen2";

                Console.WriteLine("{0,12:X} {1,12:X} {2,12:X} {3,12:X} {4,4} {5}", segment.Start, segment.End, segment.CommittedEnd, segment.ReservedEnd, segment.ProcessorAffinity, type);
            }

            ClrHeap heap = runtime.Heap;
            foreach (var item in (from seg in heap.Segments
                                  group seg by seg.ProcessorAffinity into g
                                  orderby g.Key
                                  select new
                                  {
                                      Heap = g.Key,
                                      Size = g.Sum(p => (uint)p.Length)
                                  }))
            {
                Console.WriteLine("Heap {0,2}: {1:n0} bytes", item.Heap, item.Size);
            }
        }
    }

    public class ObjectHistogramItem : IComparable<ObjectHistogramItem>
    {
        public string Key;
        public HeapObjectCounter Counter;

        public int CompareTo(ObjectHistogramItem other)
        {
            return Counter.Total.CompareTo(other.Counter.Total);
        }
    }

    public class HeapObjectCounter : IEnumerable<KeyValuePair<ulong, int>>
    {
        Dictionary<ulong, int> _owners = new Dictionary<ulong, int>();
        int _total;

        public HeapObjectCounter(ulong address)
        {
            AddCount(address);
        }

        public int OwnerCount
        {
            get
            {
                return _owners.Keys.Count;
            }
        }

        public int Total
        {
            get
            {
                return _total; // _owners.Values.Sum();
            }
        }

        public IEnumerator<KeyValuePair<ulong, int>> GetEnumerator()
        {
            return this._owners.GetEnumerator();
        }

        internal void AddCount(ulong objAddress)
        {
            if (_owners.ContainsKey(objAddress) == true)
            {
                _owners[objAddress]++;
            }
            else
            {
                _owners[objAddress] = 1;
            }

            _total++;
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return this._owners.GetEnumerator();
        }
    }
}

그래서 위의 프로그램을 이 글에서 만든 예제 프로젝트의 덤프에 대해 실행하면,

c:\temp\RefOwner\bin> RefOwner32.exe c:\tmp\ConsoleApp1.dmp System.String
Found CLR Version: v4.7.3260.00
Filesize:  6EF000
Timestamp: 5BB7BCB7
Dac File:  mscordacwks_X86_X86_4.7.3260.00.dll
Local dac location: C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscordacwks.dll

System.String, # of instances: 100034
1 System.AppDomainSetup(1)
31 System.Object[](3)
100003 System.String[](2)
Total: 100035

총 100,034개의 문자열 인스턴스 중에 무려 100,003개가 단 2개의 System.String[] 객체에 참조되고 있는 것을 볼 수 있습니다. 여기서 "/d" 옵션을 주면 2개의 System.String[]에 대한 인스턴스 주소와 그것들이 각각 참조하고 있는 System.String 객체의 수를 알 수 있습니다.

c:\temp\RefOwner\bin> RefOwner32.exe /d c:\tmp\ConsoleApp1.dmp System.String
...[생략]...
System.String, # of instances: 100034
System.String, # of instances: 100034
1 System.AppDomainSetup(1)
        [2b714e0, 1]
31 System.Object[](3)
        [3b71020, 1]
        [3b72300, 2]
        [3b72520, 28]
100003 System.String[](2)
        [2b7156c, 3]
        [3bd5570, 100000]
Total: 100035

보는 바와 같이, 두 번째 인스턴스인 3bd5570 객체가 100,000개의 String을 담고 있는데, 테스트 코드에서 100,000번의 루프로 Guid 문자열을 담은 것과 일치하는군요. ^^

windbg에서 위의 주소에 따라 dump를 해보면,

0:000> !do 3bd5570
Name:        System.String[]
MethodTable: 718ff698
EEClass:     714d4b80
Size:        524300(0x8000c) bytes
Array:       Rank 1, Number of elements 131072, Type CLASS (Print Array)
Fields:
None

배열의 크기 자체는 131,072라고 합니다. 즉, 그 배열 중에 String이 할당된 것은 100,000개이고 이후의 요소들은 모두 null입니다. 그럼, 여기서 다시 3bd5570 객체의 소유주를 보면,

c:\temp\RefOwner\bin\x86> RefOwner32.exe /d c:\tmp\ConsoleApp1.dmp 3bd5570
Found CLR Version: v4.7.3260.00
Filesize:  6EF000
Timestamp: 5BB7BCB7
Dac File:  mscordacwks_X86_X86_4.7.3260.00.dll
Local dac location: C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscordacwks.dll

3bd5570, # of instances: 1
1 System.Collections.Generic.List<System.String>(1)
        [2b720a4, 1]
Total: 1

답이 나왔습니다. (만들어 놓고 보니 그런대로 쓸만한 도구가 된 듯합니다. ^^)




참고로, ClrMD 라이브러리 사용 시 다음과 같은 오류가 발생할 수 있습니다.

System.InvalidOperationException
  HResult=0x80131509
  Message=Mismatched architecture between this process and the dac.
  Source=Microsoft.Diagnostics.Runtime
  StackTrace:
   at Microsoft.Diagnostics.Runtime.DataTarget.ConstructRuntime(ClrInfo clrInfo, String dac) in /_/src/Microsoft.Diagnostics.Runtime/src/DataTargets/DataTarget.cs:line 326
   at Microsoft.Diagnostics.Runtime.DataTarget.CreateRuntime(ClrInfo clrInfo, String dacFilename, Boolean ignoreMismatch) in /_/src/Microsoft.Diagnostics.Runtime/src/DataTargets/DataTarget.cs:line 320
   at Microsoft.Diagnostics.Runtime.ClrInfo.CreateRuntime(String dacFilename, Boolean ignoreMismatch) in /_/src/Microsoft.Diagnostics.Runtime/src/DataTargets/ClrInfo.cs:line 115
   at RefOwner.Program.Main(String[] args) in c:\temp\RefOwner\RefOwner\RefOwner\Program.cs:line 39

이유는? 현재 프로세스는 x86이면서, 분석 대상이 되는 덤프는 x64 프로세스를 뜬 경우에 해당합니다. 따라서 분석하려는 프로세스를 x64로 빌드해 다시 실행하면 정상적으로 동작합니다.




이 글의 소스 코드는 https://github.com/stjeong/RefOwner/에도 있습니다.




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 2/11/2019]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 46  47  48  49  50  [51]  52  53  54  55  56  57  58  59  60  ...
NoWriterDateCnt.TitleFile(s)
12368정성태10/12/20209979오류 유형: 668. VSIX 확장 빌드 - The "GetDeploymentPathFromVsixManifest" task failed unexpectedly.
12367정성태10/12/202022777오류 유형: 667. Ubuntu - Temporary failure resolving 'kr.archive.ubuntu.com' [2]
12366정성태10/12/202011725.NET Framework: 950. C# 9.0 - (4) 원시 크기 정수(Native ints) [1]파일 다운로드1
12365정성태10/12/202010628.NET Framework: 949. C# 9.0 - (3) 람다 메서드의 매개 변수 무시(Lambda discard parameters)파일 다운로드1
12364정성태10/11/202011821.NET Framework: 948. C# 9.0 - (2) localsinit 플래그 내보내기 무시(Suppress emitting localsinit flag)파일 다운로드1
12363정성태10/11/202012747.NET Framework: 947. C# 9.0 - (1) 대상으로 형식화된 new 식(Target-typed new expressions) [2]파일 다운로드1
12362정성태10/11/20209557VS.NET IDE: 151. Visual Studio 2019에 .NET 5 rc/preview 적용하는 방법
12361정성태10/11/202011177.NET Framework: 946. C# 9.0을 위한 개발 환경 구성
12360정성태10/8/20208372오류 유형: 666. The type or namespace name '...' does not exist in the namespace 'Microsoft.VisualStudio.TestTools' (are you missing an assembly reference?)
12359정성태10/7/20209894오류 유형: 665. Windows - 재부팅 후 iSCSI 연결이 끊기는 문제
12358정성태10/7/20209934오류 유형: 664. Web Deploy 설치 시 "A newer version of Microsoft Web Deploy 3.6 was found on this machine." 오류 [3]
12357정성태10/7/20207993오류 유형: 663. 이벤트 로그 - The storage optimizer couldn't complete retrim on New Volume
12356정성태10/7/202022927오류 유형: 662. ASP.NET Core와 500.19, 500.21 오류 (0x8007000d)
12355정성태10/3/20208071오류 유형: 661. Hyper-V Linux VM의 Internal 유형의 가상 Switch에 대한 IP 연결이 되지 않는 경우
12354정성태10/2/202021006오류 유형: 660. Web Deploy (msdeploy.axd) 실행 시 오류 기록 [1]
12353정성태10/2/202010801개발 환경 구성: 518. 비주얼 스튜디오에서 IIS 웹 서버로 "Web Deploy"를 이용해 배포하는 방법
12352정성태10/2/202011282개발 환경 구성: 517. Hyper-V Internal 네트워크에 NAT을 이용한 인터넷 연결 제공
12351정성태10/2/202010826오류 유형: 659. Nox 실행이 안 되는 경우 - Unable to bind to the underlying transport for ...
12350정성태9/25/202014320Windows: 175. 윈도우 환경에서 클라이언트 소켓의 최대 접속 수 [2]파일 다운로드1
12349정성태9/25/20209195Linux: 32. Ubuntu 20.04 - docker를 위한 tcp 바인딩 추가
12348정성태9/25/20209936오류 유형: 658. 리눅스 docker - Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock
12347정성태9/25/202024093Windows: 174. WSL 2의 네트워크 통신 방법 [4]
12346정성태9/25/20209184오류 유형: 657. IIS - http://localhost 방문 시 Service Unavailable 503 오류 발생
12345정성태9/25/20208866오류 유형: 656. iisreset 실행 시 "Restart attempt failed." 오류가 발생하지만 웹 서비스는 정상적인 경우파일 다운로드1
12344정성태9/25/202010035Windows: 173. 서비스 관리자에 "IIS Admin Service"가 등록되어 있지 않다면?
12343정성태9/24/202019728.NET Framework: 945. C# - 닷넷 응용 프로그램에서 메모리 누수가 발생할 수 있는 패턴 [5]
... 46  47  48  49  50  [51]  52  53  54  55  56  57  58  59  60  ...