2012-02-29 2 views
16

Почему в приведенном ниже сценарии пространство стека увеличивается в x64, но уменьшается в x32 с идентичным кодом?Разный стиль разматывания стека в x64 и x32

фона:

Наши клиенты могут писать сценарии на языке домена, который во время выполнения интерпретируются с использованием рекурсивного техники и выполняется на веб-сервере. Вполне возможно, что они могут сделать ошибку в скрипте, который генерирует исключение, это исключение попадает и регистрируется.

Из-за этой рекурсивной техники мы защищаем исключения переполнения стека, проверяя пространство стека, используемое в том случае, когда интерпретатор выполняет сценарий и завершает сценарий до того, как мы на самом деле закончим стеки.

В 32-битном режиме все работает хорошо, когда скриптером делает ошибку Exception генерируется, он вошел в систему, стек раскручивается, в течение которого осталось в стеке увеличивается и сценарий пространства завершается красиво.

В режиме 64-битных все не так хорошо, когда скриптер делает ошибку Exception генерируется, он вошел в системе, стек раскручивается, в течение которого остались в стеке пространства уменьшается. Это очень плохо, потому что есть вероятность, что если скрипт использовал много пространства стека и выбрасывает, то акт разматывания стека и регистрации самой ошибки вызывает StackOverflowException, который скрывает исходное исключение, шланги IIS и убивает все запросы на вход (плохое, очень плохое и действительно очень плохое).

Воссоздание проблемы:

Вот приложение консоли, которая моделирует код я использую в производстве и воссоздает проблему, когда установлена ​​в 64, и отлично работает в x32.

using System; 
using System.ComponentModel; 
using System.Runtime.InteropServices; 

namespace ConsoleApplication16 
{ 
    class Program 
    { 
     const Int32 MaxNumberRecursions = 10; 
     static Int32 _currentRecursionDepth; 
     static UInt64 _lastSpaceUsed; 

     static void Main(string[] args) 
     { 
      System.Diagnostics.Debug.WriteLine(String.Format("Is64BitProcess = {0}", System.Environment.Is64BitProcess)); 
      try 
      { 
       _lastSpaceUsed = GetStackBytesLeft(); 
       RecurseXTimes(); 
      } 
      catch (Exception e) 
      { 
       System.Diagnostics.Debug.WriteLine(e); 
      } 
     } 

     unsafe static void RecurseXTimes() 
     { 
      System.Diagnostics.Debug.WriteLine("--> RecurseXTimes()"); 
      ReportStackSpaceUsage(); 

      try 
      { 
       _currentRecursionDepth++; 
       if (_currentRecursionDepth > MaxNumberRecursions) 
       { 
        throw new Exception("Please unwind my stack"); 
       } 

       System.Diagnostics.Debug.WriteLine(String.Format("Adding {0} bytes to stack.", sizeof(SomeDataToUseUpSomeStackSpace)));     
       SomeDataToUseUpSomeStackSpace someDataToUseUpSomeStackSpace = new SomeDataToUseUpSomeStackSpace(); 

       RecurseXTimes(); 
      } 
      catch(Exception e) 
      { 
       //Do some logging. NOTE taking this "catch" out "fixes" the problem, but I can't do this in prod. 
       System.Diagnostics.Debug.WriteLine(e.Message); 
       throw; 
      } 
      finally 
      { 
       ReportStackSpaceUsage(); 
       System.Diagnostics.Debug.WriteLine("<-- RecurseXTimes()"); 
      } 
     } 

     private static void ReportStackSpaceUsage() 
     { 
      UInt64 stackUsed = GetStackBytesLeft(); 
      Int64 stackSpaceDelta = (Int64) stackUsed - (Int64) _lastSpaceUsed; 
      Int64 stackSpaceDeltaAbs = Math.Abs(stackSpaceDelta); 

      System.Diagnostics.Debug.WriteLine(
       String.Format("Stack space left: {0}. Stack Space Delta: {1} {2}", 
           stackUsed, 
           stackSpaceDeltaAbs, 
           stackSpaceDelta < 0 ? "Allocated On Stack" : "Freed from Stack")); 

      _lastSpaceUsed = stackUsed; 
     } 


     static unsafe ulong GetStackBytesLeft() 
     { 
      MEMORY_BASIC_INFORMATION stackInfo = new MEMORY_BASIC_INFORMATION(); 
      UIntPtr currentAddr = new UIntPtr(&stackInfo); 
      int sizeT = VirtualQuery(currentAddr, ref stackInfo, sizeof(MEMORY_BASIC_INFORMATION)); 

      if (sizeT == 0) 
      { 
       //No Data Returned 
       int lastError = Marshal.GetLastWin32Error(); 
       throw new Win32Exception(lastError); 
      } 

      UInt64 stackBytesLeft = currentAddr.ToUInt64() - stackInfo.AllocationBase.ToUInt64(); 
      return stackBytesLeft; 
     } 

     [DllImport("kernel32.dll", SetLastError = true)] 
     static extern int VirtualQuery(UIntPtr lpAddress, ref MEMORY_BASIC_INFORMATION lpBuffer, int dwLength); 

     [StructLayout(LayoutKind.Sequential)] 
     struct MEMORY_BASIC_INFORMATION 
     { 
      public UIntPtr BaseAddress; 
      public UIntPtr AllocationBase; 
      public uint AllocationProtect; 
      public UIntPtr RegionSize; 
      public uint State; 
      public uint Protect; 
      public uint Type; 
     }; 

     private struct SomeDataToUseUpSomeStackSpace 
     { 
      public Int64 a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25; 
      public Int64 b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16, b17, b18, b19, b20, b21, b22, b23, b24, b25; 
      public Int64 c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14, c15, c16, c17, c18, c19, c20, c21, c22, c23, c24, c25; 
      public Int64 d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14, d15, d16, d17, d18, d19, d20, d21, d22, d23, d24, d25; 
      public Int64 e0, e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15, e16, e17, e18, e19, e20, e21, e22, e23, e24, e25; 
      public Int64 f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, f16, f17, f18, f19, f20, f21, f22, f23, f24, f25; 
      public Int64 g0, g1, g2, g3, g4, g5, g6, g7, g8, g9, g10, g11, g12, g13, g14, g15, g16, g17, g18, g19, g20, g21, g22, g23, g24, g25; 
      public Int64 h0, h1, h2, h3, h4, h5, h6, h7, h8, h9, h10, h11, h12, h13, h14, h15, h16, h17, h18, h19, h20, h21, h22, h23, h24, h25; 
      public Int64 i0, i1, i2, i3, i4, i5, i6, i7, i8, i9, i10, i11, i12, i13, i14, i15, i16, i17, i18, i19, i20, i21, i22, i23, i24, i25; 
      public Int64 j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15, j16, j17, j18, j19, j20, j21, j22, j23, j24, j25; 
      public Int64 k0, k1, k2, k3, k4, k5, k6, k7, k8, k9, k10, k11, k12, k13, k14, k15, k16, k17, k18, k19, k20, k21, k22, k23, k24, k25; 
      public Int64 l0, l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15, l16, l17, l18, l19, l20, l21, l22, l23, l24, l25; 
      public Int64 m0, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15, m16, m17, m18, m19, m20, m21, m22, m23, m24, m25; 
      public Int64 n0, n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12, n13, n14, n15, n16, n17, n18, n19, n20, n21, n22, n23, n24, n25; 
      public Int64 o0, o1, o2, o3, o4, o5, o6, o7, o8, o9, o10, o11, o12, o13, o14, o15, o16, o17, o18, o19, o20, o21, o22, o23, o24, o25; 
      public Int64 p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18, p19, p20, p21, p22, p23, p24, p25; 
      public Int64 q0, q1, q2, q3, q4, q5, q6, q7, q8, q9, q10, q11, q12, q13, q14, q15, q16, q17, q18, q19, q20, q21, q22, q23, q24, q25; 
      public Int64 r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, r16, r17, r18, r19, r20, r21, r22, r23, r24, r25; 
      public Int64 s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15, s16, s17, s18, s19, s20, s21, s22, s23, s24, s25; 
      public Int64 t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16, t17, t18, t19, t20, t21, t22, t23, t24, t25; 
      public Int64 u0, u1, u2, u3, u4, u5, u6, u7, u8, u9, u10, u11, u12, u13, u14, u15, u16, u17, u18, u19, u20, u21, u22, u23, u24, u25; 
      public Int64 v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25; 
      public Int64 w0, w1, w2, w3, w4, w5, w6, w7, w8, w9, w10, w11, w12, w13, w14, w15, w16, w17, w18, w19, w20, w21, w22, w23, w24, w25; 
      public Int64 x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22, x23, x24, x25; 
      public Int64 y0, y1, y2, y3, y4, y5, y6, y7, y8, y9, y10, y11, y12, y13, y14, y15, y16, y17, y18, y19, y20, y21, y22, y23, y24, y25; 
      public Int64 z0, z1, z2, z3, z4, z5, z6, z7, z8, z9, z10, z11, z12, z13, z14, z15, z16, z17, z18, z19, z20, z21, z22, z23, z24, z25; 
     } 
    } 
} 

Результат:

x32 - все денди, стека уменьшается вист по убыванию, и увеличивается во время разматывания.

Is64BitProcess = False 
--> RecurseXTimes() 
Stack space left: 1036512. Stack Space Delta: 5652 Allocated On Stack 
Adding 5408 bytes to stack. 
--> RecurseXTimes() 
Stack space left: 1031004. Stack Space Delta: 5508 Allocated On Stack 
Adding 5408 bytes to stack. 
--> RecurseXTimes() 
Stack space left: 1025496. Stack Space Delta: 5508 Allocated On Stack 
Adding 5408 bytes to stack. 
--> RecurseXTimes() 
Stack space left: 1019988. Stack Space Delta: 5508 Allocated On Stack 
Adding 5408 bytes to stack. 
--> RecurseXTimes() 
Stack space left: 1014480. Stack Space Delta: 5508 Allocated On Stack 
Adding 5408 bytes to stack. 
--> RecurseXTimes() 
Stack space left: 1008972. Stack Space Delta: 5508 Allocated On Stack 
Adding 5408 bytes to stack. 
--> RecurseXTimes() 
Stack space left: 1003464. Stack Space Delta: 5508 Allocated On Stack 
Adding 5408 bytes to stack. 
--> RecurseXTimes() 
Stack space left: 997956. Stack Space Delta: 5508 Allocated On Stack 
Adding 5408 bytes to stack. 
--> RecurseXTimes() 
Stack space left: 992448. Stack Space Delta: 5508 Allocated On Stack 
Adding 5408 bytes to stack. 
--> RecurseXTimes() 
Stack space left: 986940. Stack Space Delta: 5508 Allocated On Stack 
Adding 5408 bytes to stack. 
--> RecurseXTimes() 
Stack space left: 981432. Stack Space Delta: 5508 Allocated On Stack 
A first chance exception of type 'System.Exception' occurred in ConsoleApplication16.exe 
Please unwind my stack 
Stack space left: 976816. Stack Space Delta: 4616 Allocated On Stack 
A first chance exception of type 'System.Exception' occurred in ConsoleApplication16.exe 
<-- RecurseXTimes() 
Please unwind my stack 
A first chance exception of type 'System.Exception' occurred in ConsoleApplication16.exe 
Stack space left: 982328. Stack Space Delta: 5512 Freed from Stack 
<-- RecurseXTimes() 
Please unwind my stack 
Stack space left: 987832. Stack Space Delta: 5504 Freed from Stack 
<-- RecurseXTimes() 
Please unwind my stack 
Stack space left: 993344. Stack Space Delta: 5512 Freed from Stack 
<-- RecurseXTimes() 
Please unwind my stack 
Stack space left: 998848. Stack Space Delta: 5504 Freed from Stack 
<-- RecurseXTimes() 
Please unwind my stack 
Stack space left: 1004360. Stack Space Delta: 5512 Freed from Stack 
<-- RecurseXTimes() 
Please unwind my stack 
Stack space left: 1009864. Stack Space Delta: 5504 Freed from Stack 
<-- RecurseXTimes() 
Please unwind my stack 
Stack space left: 1015376. Stack Space Delta: 5512 Freed from Stack 
<-- RecurseXTimes() 
Please unwind my stack 
Stack space left: 1020880. Stack Space Delta: 5504 Freed from Stack 
<-- RecurseXTimes() 
Please unwind my stack 
Stack space left: 1026392. Stack Space Delta: 5512 Freed from Stack 
<-- RecurseXTimes() 
Please unwind my stack 
Stack space left: 1031896. Stack Space Delta: 5504 Freed from Stack 
<-- RecurseXTimes() 
System.Exception: Please unwind my stack 

Вот результат с точно такой же код в x64, стек пространства увеличивается в то время как По убыванию, но продолжает снижаться во время размотки:

Is64BitProcess = True 
--> RecurseXTimes() 
Stack space left: 1034256. Stack Space Delta: 5696 Allocated On Stack 
Adding 5408 bytes to stack. 
--> RecurseXTimes() 
Stack space left: 1028704. Stack Space Delta: 5552 Allocated On Stack 
Adding 5408 bytes to stack. 
--> RecurseXTimes() 
Stack space left: 1023152. Stack Space Delta: 5552 Allocated On Stack 
Adding 5408 bytes to stack. 
--> RecurseXTimes() 
Stack space left: 1017600. Stack Space Delta: 5552 Allocated On Stack 
Adding 5408 bytes to stack. 
--> RecurseXTimes() 
Stack space left: 1012048. Stack Space Delta: 5552 Allocated On Stack 
Adding 5408 bytes to stack. 
--> RecurseXTimes() 
Stack space left: 1006496. Stack Space Delta: 5552 Allocated On Stack 
Adding 5408 bytes to stack. 
--> RecurseXTimes() 
Stack space left: 1000944. Stack Space Delta: 5552 Allocated On Stack 
Adding 5408 bytes to stack. 
--> RecurseXTimes() 
Stack space left: 995392. Stack Space Delta: 5552 Allocated On Stack 
Adding 5408 bytes to stack. 
--> RecurseXTimes() 
Stack space left: 989840. Stack Space Delta: 5552 Allocated On Stack 
Adding 5408 bytes to stack. 
--> RecurseXTimes() 
Stack space left: 984288. Stack Space Delta: 5552 Allocated On Stack 
Adding 5408 bytes to stack. 
--> RecurseXTimes() 
Stack space left: 978736. Stack Space Delta: 5552 Allocated On Stack 
A first chance exception of type 'System.Exception' occurred in ConsoleApplication16.exe 
Please unwind my stack 
A first chance exception of type 'System.Exception' occurred in ConsoleApplication16.exe 
Stack space left: 957392. Stack Space Delta: 21344 Allocated On Stack 
<-- RecurseXTimes() 
Please unwind my stack 
A first chance exception of type 'System.Exception' occurred in ConsoleApplication16.exe 
Stack space left: 948880. Stack Space Delta: 8512 Allocated On Stack 
<-- RecurseXTimes() 
Please unwind my stack 
A first chance exception of type 'System.Exception' occurred in ConsoleApplication16.exe 
Stack space left: 940368. Stack Space Delta: 8512 Allocated On Stack 
<-- RecurseXTimes() 
Please unwind my stack 
Stack space left: 931856. Stack Space Delta: 8512 Allocated On Stack 
<-- RecurseXTimes() 
Please unwind my stack 
Stack space left: 923344. Stack Space Delta: 8512 Allocated On Stack 
<-- RecurseXTimes() 
Please unwind my stack 
Stack space left: 914832. Stack Space Delta: 8512 Allocated On Stack 
<-- RecurseXTimes() 
Please unwind my stack 
A first chance exception of type 'System.Exception' occurred in ConsoleApplication16.exe 
A first chance exception of type 'System.Exception' occurred in ConsoleApplication16.exe 
A first chance exception of type 'System.Exception' occurred in ConsoleApplication16.exe 
A first chance exception of type 'System.Exception' occurred in ConsoleApplication16.exe 
Stack space left: 906320. Stack Space Delta: 8512 Allocated On Stack 
<-- RecurseXTimes() 
Please unwind my stack 
A first chance exception of type 'System.Exception' occurred in ConsoleApplication16.exe 
Stack space left: 897808. Stack Space Delta: 8512 Allocated On Stack 
<-- RecurseXTimes() 
Please unwind my stack 
A first chance exception of type 'System.Exception' occurred in ConsoleApplication16.exe 
Stack space left: 889296. Stack Space Delta: 8512 Allocated On Stack 
<-- RecurseXTimes() 
Please unwind my stack 
A first chance exception of type 'System.Exception' occurred in ConsoleApplication16.exe 
Stack space left: 880784. Stack Space Delta: 8512 Allocated On Stack 
<-- RecurseXTimes() 
Please unwind my stack 
A first chance exception of type 'System.Exception' occurred in ConsoleApplication16.exe 
Stack space left: 872272. Stack Space Delta: 8512 Allocated On Stack 
<-- RecurseXTimes() 
System.Exception: Please unwind my stack 

Как ни странно, если я удалить «поймать», то под 64-разрядный он поддерживает стек, который используется одинаково, но я не могу сделать это в prodcuction, потому что мне нужно делать записи в catch.

+0

Не должны ли вы запускать эти сценарии в виде песочницы (VM), чтобы предотвратить их полное уничтожение всего сервера? –

+0

Мы исследовали это с помощью AppDomains, но они слишком тяжелы, чтобы создать новый AppDomain для каждого запроса. Также нам нравится использовать IIS, и мы не можем найти способ в IIS, чтобы изменить его поведение хоста ICLRPolicyManager для StackOverflow (мы хотели бы зарегистрировать их и не прекратить процесс). Таким образом, нам нужен автор нашего собственного хоста Application Domain, и мы будем делать всю обработку http и т. Д. И т. Д. - это большая работа. Подход, который мы имеем сейчас, работает очень хорошо, в режиме x32! Просто не в x64! –

+0

У меня есть память о том, что 32 и 64-битные CLR отличаются при обработке рекурсии хвостового вызова, но я не могу найти ссылку прямо сейчас. – AakashM

ответ

-2

Рекурсия, которая использует много памяти, подобную этой, возможно, делает это в куче, используя объект Stack. Вы можете увеличить/уменьшить его самостоятельно и извлечь выгоду из гораздо большего объема памяти.

1

Кажется, что дополнительный блок памяти выделяется при повторном выбросе исключения. Таким образом, вы все равно можете получить в своем производственном кодексе.

Обширные исследования (хорошо на самом деле) показывают, что x64 stack unwind metadata может быть cuplrit для дополнительных распределений в режиме x64.

Я изменил поймать исключение в RecurseXTimes в примере кода, так что:

  • Регистрирует исключение в настоящее время
  • Сериализует его через BinaryFormatter

И в конце следующего наконец, Deserialze и бросить его. Трассировка стека теперь выглядит довольно похоже на 32- и 64-битный режим.

Хорошо, так что вы теряете видимость сайта исключения, но его рекурсивный так или иначе, так что, по крайней мере, имя функции правильное .. и вам, возможно, придется разобраться, как вы можете выделить достаточно памяти для сериализации!

[Редактировать] Вот твик я сделал:

 catch (Exception e) 
     { 
      //Do some logging. NOTE taking this "catch" out "fixes" the problem, but I can't do this in prod. 
      System.Diagnostics.Debug.WriteLine(e.Message); 
      caughtException = new MemoryStream(); 
      BinaryFormatter exceptionFormatter = new BinaryFormatter(); // Exception raised on this line 
      exceptionFormatter.Serialize(caughtException, e); 
      caughtException.Seek(0, SeekOrigin.Begin); 
     } 
     finally 
     { 
      ReportStackSpaceUsage(); 
      System.Diagnostics.Debug.WriteLine("<-- RecurseXTimes()"); 
      if (caughtException != null) 
      { 
       BinaryFormatter exceptionFormatter = new BinaryFormatter(); 
       Exception e = (Exception)exceptionFormatter.Deserialize(caughtException); 
       throw e; 
      } 
     } 

Надеюсь, в вашем реальном приложении есть достаточно запас сделать это без вторичного OutOfMemoryException.