Nej, der mangler ikke et “med” i overskriften. Dette indlæg handler om en fejl, jeg fandt i ADPlus.
I den seneste version af Debugging Tools for Windows er det gamle ADPlus vb-script blevet udskiftet med en exe-fil. ADPlus er blevet genimplementeret som en managed applikation (det gamle script er stadig tilgængelig under navnet adplus_old.vbs).
Ifølge det medfølgende Word-dokument er dette gjort for bedre at kunne udvide ADPlus med ny funktionalitet, og der er allerede kommet et par yderligere muligheder som f.eks. –pmn, der overvåger alle processer med et bestemt navn.
Det er dog ikke det, vi skal se på i dette indlæg. Ligesom sin forgænger understøtter adplus.exe, at vi kan få debuggeren til at starte vores applikation via –sc. Desværre er denne feature ikke implementeret særlig elegant i den nye version.
I forbindelse med en debugging-opgave havde jeg brug for at starte min applikation via –sc, men uanset hvad jeg gjorde, fik jeg nedenstående fejlmeddelelse:
Spawning c:\dev2010\testapp\testapp\bin\release\testapp.exe
!!! ERROR !!!
The system cannot find the file specified
at System.Diagnostics.Process.StartWithShellExecuteEx(ProcessStartInfo startInfo)
at System.Diagnostics.Process.Start(ProcessStartInfo startInfo)
at ADPlus.AdplusEngine.SpawnProcess(String SpawnCommand)
at ADPlus.AdplusApl.TryAttachSelectedProcesses()
at ADPlus.AdplusApl.TryRun()
!!!ERROR - ADPlus failed to run
Det fik mig til at tro, at ADPlus af en eller anden grund ikke kunne finde min applikation, men det er faktisk ikke det, fejlmeddelelsen angiver.
I mangel på bedre fyrede jeg op under WinDbg for at finde ud af, hvorfor denne fejl opstod.
adplus.exe er som sagt en managed applikation, og den er bygget til CLR version 2 og AnyCPU, så vores runtime hedder mscorwks, og vi skal have fat i 64 bit versionen af WinDbg på trods af, at vi ønsker at debugge den version, der kommer med 32 bit versionen af Debugging Tools for Windows.
Via Open Executable-dialogen kan vi udvælge adplus.exe og angive de relevante opstartsparametre. Herefter starter WinDbg adplus.exe, men stopper udførelsen, så snart processen er i luften. På dette tidspunkt er CLRen ikke startet endnu, så vi får ikke noget ud af at forsøge at indlæse SOS. I stedet kan vi sætte et event filter så eksekveringen stopper, når CLRen indlæses og derefter indlæse SOS. Det kan gøres som følger:
sxe -c ".loadby sos mscorwks" ld mscorwks
Herefter kører vi videre, indtil vi rammer vores event filter og får indlæst SOS.
0:000> g
ModLoad: 000007fe`efd10000 000007fe`f06be000 C:\Windows\Microsoft.NET\Framework64\v2.0.50727\mscorwks.dll
ntdll!ZwMapViewOfSection+0xa:
00000000`77b9ffda c3 ret
Vi ved fra fejludskriften, at der bliver smidt en exception, så lad os sætte et event filter for managed exceptions.
0:000> sxe -c "!clrstack; !pe" clr
Ovenstående stopper i tilfælde af en managed exception og udskriver derefter kaldestak for den aktuelle tråd samt detaljerne om vores exception. Og så kører vi igen:
0:000> g
(14c4.13fc): CLR exception - code e0434f4d (first chance)
OS Thread Id: 0x13fc (0)
*** WARNING: Unable to verify checksum for C:\Windows\assembly\NativeImages_v2.0.50727_64\System\247913fa7ae6fcf04ea33d28d24ab611\System.ni.dll
Child-SP RetAddr Call Site
00000000002eec30 000007feefa406bc System.Diagnostics.Process.StartWithShellExecuteEx(System.Diagnostics.ProcessStartInfo)
00000000002eed30 000007ff00198597 System.Diagnostics.Process.Start(System.Diagnostics.ProcessStartInfo)
00000000002eed70 000007ff00197ef0 ADPlus.AdplusEngine.SpawnProcess(System.String)
00000000002eedc0 000007ff001942ce ADPlus.AdplusApl.TryAttachSelectedProcesses()
00000000002eee50 000007ff00191d98 ADPlus.AdplusApl.TryRun()
00000000002eeec0 000007ff0019026e ADPlus.AdplusApl.TryExecute(System.String[])
00000000002eef00 000007feeffdd502 ADPlus.Program.Main(System.String[])
Exception object: 000000000265fe10
Exception type: System.ComponentModel.Win32Exception
Message: The system cannot find the file specified
InnerException: <none>
StackTrace (generated):
<none>
StackTraceString: <none>
HResult: 80004005
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
KERNELBASE!RaiseException+0x39:
000007fe`fdb6aa7d 4881c4c8000000 add rsp,0C8h
Debuggeren er stoppet som følge af en exception. Det ser ud til at passe meget godt med vores fejlsituation, så nu skal vi blot finde ud af, hvad det er for en fil, der ikke kan lokaliseres (tænk på hvor meget lettere alt ville være, hvis vi bare kunne læse det ud af vores exception).
Via kaldestakken kan vi se, at StartWithShellExecuteEx bliver kaldt med en instans af ProcessStartInfo. Hvis vi er heldige, er der en reference til denne på stakken. På 64 bit Windows overføres de fire første argumenter altid i registre, så vi kan ikke regne med at finde noget på stakken. I visse situationen opretter compileren dog lokale referencer på stakken, så det er vores første forsøg:
0:000> !dso
OS Thread Id: 0x13fc (0)
RSP/REG Object Name
00000000002eea30 000000000265fef8 System.Text.StringBuilder
00000000002eea68 000000000265fe10 System.ComponentModel.Win32Exception
00000000002eea70 00000000025a7b48 System.String
00000000002eea80 000000000265fe10 System.ComponentModel.Win32Exception
00000000002eeaa0 000000000265fe10 System.ComponentModel.Win32Exception
00000000002eeaf0 000000000265ff20 System.String
00000000002eeb00 0000000002660140 System.String
00000000002eeb70 000000000265fe10 System.ComponentModel.Win32Exception
00000000002eeb78 00000000025a7b48 System.String
00000000002eeb80 000000000265fe10 System.ComponentModel.Win32Exception
00000000002eebd8 00000000025a7b48 System.String
00000000002eebe0 000000000265fe10 System.ComponentModel.Win32Exception
00000000002eec10 000000000265fdf0 System.Diagnostics.ShellExecuteHelper
00000000002eec20 000000000265fe10 System.ComponentModel.Win32Exception
00000000002eec60 000000000265fbb8 System.Diagnostics.Process
00000000002eec68 00000000025a7b48 System.String
00000000002eec70 000000000265f238 System.Diagnostics.ProcessStartInfo
00000000002eece0 000000000265fcc8 Microsoft.Win32.NativeMethods+ShellExecuteInfo
00000000002eed10 000000000265f238 System.Diagnostics.ProcessStartInfo
00000000002eed18 00000000025a7a98 ADPlus.AdplusEngine
00000000002eed20 000000000265fbb8 System.Diagnostics.Process
00000000002eed30 000000000265fbb8 System.Diagnostics.Process
00000000002eed38 000000000265f238 System.Diagnostics.ProcessStartInfo
00000000002eed50 00000000025a7b48 System.String
00000000002eed58 000000000265f238 System.Diagnostics.ProcessStartInfo
00000000002eed60 000000000265f130 System.String
...
Jeg har forkortet listen en smule, men som vi kan se, er der faktisk en reference til en instans af ProcessStartInfo. Heldigt, så behøver vi ikke grave videre. Lad os se nærmere på instansen.
0:000> !do 000000000265f238
Name: System.Diagnostics.ProcessStartInfo
MethodTable: 000007feefb3ce18
EEClass: 000007feef39dcb8
Size: 128(0x80) bytes
(C:\Windows\assembly\GAC_MSIL\System\2.0.0.0__b77a5c561934e089\System.dll)
Fields:
MT Field Offset Type VT Attr Value Name
000007feee37ec90 4003376 8 System.String 0 instance 00000000025a7b48 fileName
000007feee37ec90 4003377 10 System.String 0 instance 000000000265f130 arguments
000007feee37ec90 4003378 18 System.String 0 instance 0000000000000000 directory
000007feee37ec90 4003379 20 System.String 0 instance 0000000000000000 verb
000007feefb0ace0 400337a 68 System.Int32 1 instance 2 windowStyle
000007feee37de60 400337b 6c System.Boolean 1 instance 0 errorDialog
000007feee381698 400337c 60 System.IntPtr 1 instance 0 errorDialogParentHandle
000007feee37de60 400337d 6d System.Boolean 1 instance 1 useShellExecute
000007feee37ec90 400337e 28 System.String 0 instance 0000000000000000 userName
000007feee37ec90 400337f 30 System.String 0 instance 0000000000000000 domain
000007feeeb78b50 4003380 38 ...rity.SecureString 0 instance 0000000000000000 password
000007feee37de60 4003381 6e System.Boolean 1 instance 0 loadUserProfile
000007feee37de60 4003382 6f System.Boolean 1 instance 0 redirectStandardInput
000007feee37de60 4003383 70 System.Boolean 1 instance 0 redirectStandardOutput
000007feee37de60 4003384 71 System.Boolean 1 instance 0 redirectStandardError
000007feee386ae0 4003385 40 System.Text.Encoding 0 instance 0000000000000000 standardOutputEncoding
000007feee386ae0 4003386 48 System.Text.Encoding 0 instance 0000000000000000 standardErrorEncoding
000007feee37de60 4003387 72 System.Boolean 1 instance 0 createNoWindow
000007feee377a90 4003388 50 System.WeakReference 0 instance 0000000000000000 weakParentProcess
000007feef5d5d68 4003389 58 ....StringDictionary 0 instance 0000000000000000 environmentVariables
Læg mærke til det første felt ved navn fileName. Det må vi hellere se nærmere på:
0:000> !do 00000000025a7b48
Name: System.String
MethodTable: 000007feee37ec90
EEClass: 000007feedf8b038
Size: 40(0x28) bytes
(C:\Windows\assembly\GAC_64\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
String: cdb.exe
Fields:
MT Field Offset Type VT Attr Value Name
000007feee385f00 4000096 8 System.Int32 1 instance 8 m_arrayLength
000007feee385f00 4000097 c System.Int32 1 instance 7 m_stringLength
000007feee3806d8 4000098 10 System.Char 1 instance 63 m_firstChar
000007feee37ec90 4000099 20 System.String 0 shared static Empty
>> Domain:Value 00000000003d0390:00000000025a1308 <<
000007feee380588 400009a 28 System.Char[] 0 shared static WhitespaceChars
>> Domain:Value 00000000003d0390:00000000025a1a38 <<
Hmm, det indeholder cdb.exe. Så det er altså der, skoen trykker. Det er ikke fordi adplus.exe ikke kan finde min applikation, men fordi den ikke kan finde cdb.exe.
Det er jo underligt, for de andre kommandoer har ingen problemer med at finde cdb.exe. Det betyder selvfølgelig, at vi kan løse problemet ved blot at tilføje det relevante directory til vores path, men nu er der faktisk en grund til, at jeg ikke har Debugging Tools for Windows i min path. Jeg har tre versioner installeret (nyeste version i 32 og 64 bit samt en 32 bit udgave af 6.7.5), og jeg vil selv kunne bestemme hvilken en jeg får fat i, og derfor har jeg ingen af disse i min path.
Det oplagte spørgsmål er: Hvorfor fejler –sc, når de andre kommandoer virker uden path? Kaldestakken fortæller os, at den relevante metode i ADPlus er SpawnProcess. Denne metode viser sig at være en instansmetode på typen AdplusEngine – adplus.exe er jo en managed applikation, så vi kan inspicere koden ved hjælp af Reflector.
Hvis vi kan kalde SpawnProcess, må der være en instans af AdplusEngine på heapen. Går vi tilbage til ovenstående liste af stakreferencer, finder vi faktisk en reference til en sådan instans. Lad os se nærmere på den.
0:000> !do 00000000025a7a98
Name: ADPlus.AdplusEngine
MethodTable: 000007ff00054068
EEClass: 000007ff00182688
Size: 176(0xb0) bytes
(C:\dbg32\adplus.exe)
Fields:
MT Field Offset Type VT Attr Value Name
0000000000000000 4000009 8 0 instance 00000000025ab618 m_KeyWords
0000000000000000 400000a 10 0 instance 00000000025ab500 m_KeyWordsCustom
0000000000000000 400000b 18 0 instance 00000000025ac580 m_Exceptions
0000000000000000 400000c 20 0 instance 00000000025ae0f0 m_Breakpoints
000007feee37ec90 400000d 28 System.String 0 instance 00000000025a8c90 m_HangConfiguration
000007feee37de60 400000e 9c System.Boolean 1 instance 0 m_HangConfigurationChanged
0000000000000000 400000f 30 0 instance 00000000025ae080 m_PreCommands
0000000000000000 4000010 38 0 instance 00000000025ae0c8 m_PostCommands
0000000000000000 4000011 40 0 instance 00000000025ae138 m_NotilyList
000007feee37ec90 4000012 48 System.String 0 instance 00000000025a32a8 m_OutputDir
000007feee37ec90 4000013 50 System.String 0 instance 00000000025b1680 m_OutputDumpDir
000007ff00053f10 4000014 98 System.Int32 1 instance 1 m_RunMode
000007feee37ec90 4000015 58 System.String 0 instance 00000000025ad9a0 m_DateTimeStamp
000007feee37ec90 4000016 60 System.String 0 instance 00000000025a1308 m_Sympath
000007feee37de60 4000017 9d System.Boolean 1 instance 1 m_SympathSet
000007feee37ec90 4000018 68 System.String 0 instance 00000000025a1308 m_MssLocalCache
000007feee37de60 4000019 9e System.Boolean 1 instance 0 m_AddMss
000007feee37ec90 400001a 70 System.String 0 instance 00000000025a1308 m_LastScriptCommand
000007feee37ec90 400001b 78 System.String 0 instance 00000000025a7b48 m_Debugger
000007feee37de60 400001c 9f System.Boolean 1 instance 0 m_ExtensionInteraction
000007feee37de60 400001d a0 System.Boolean 1 instance 0 m_ExtensionDebug
000007feee37ec90 400001e 80 System.String 0 instance 00000000025ab408 m_AdplusPath
000007feee37ec90 400001f 88 System.String 0 instance 00000000025a1308 m_DebuggersPath
000007ff00053808 4000020 90 ADPlus.AdplusApl 0 instance 00000000025a77c0 m_AdplusApl
000007feee37ec90 4000005 8 System.String 0 static 00000000025a6af0 m_Version
000007feee37ec90 4000006 10 System.String 0 static 00000000025a6b20 m_VersionDate
000007feee374748 4000007 18 System.Object[] 0 static 00000000025a6b90 m_LineSeparator
000007feee374748 4000008 20 System.Object[] 0 static 00000000025a6bb8 m_KWSeparator
Læg mærke til m_AdplusPath. Lad os se, hvad den indeholder.
0:000> !do 00000000025ab408
Name: System.String
MethodTable: 000007feee37ec90
EEClass: 000007feedf8b038
Size: 44(0x2c) bytes
(C:\Windows\assembly\GAC_64\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
String: C:\dbg32\
Fields:
MT Field Offset Type VT Attr Value Name
000007feee385f00 4000096 8 System.Int32 1 instance 10 m_arrayLength
000007feee385f00 4000097 c System.Int32 1 instance 9 m_stringLength
000007feee3806d8 4000098 10 System.Char 1 instance 43 m_firstChar
000007feee37ec90 4000099 20 System.String 0 shared static Empty
>> Domain:Value 00000000003d0390:00000000025a1308 <<
000007feee380588 400009a 28 System.Char[] 0 shared static WhitespaceChars
>> Domain:Value 00000000003d0390:00000000025a1a38 <<
Den indeholder c:\dbg32, som er en junction til min 32 bit installation af Debugging Tools for Windows.
ADPlus har altså de nødvendige oplysninger til rådighed, men glemmer åbenbart at bruge dem. Lad os se på implementeringen af SpawnProcess:
public void SpawnProcess(string SpawnCommand)
{
Logger.LogAndOut("Spawning " + SpawnCommand);
string debugger = this.m_Debugger;
string arguments = ("-c $<\"" + this.OutputDumpDir + "\\DebuggerScript.txt\" ") + SpawnCommand;
Process.Start(new ProcessStartInfo(debugger, arguments) { WindowStyle = ProcessWindowStyle.Minimized });
}
ProcessStartInfo oprettes blot med indholdet af m_Debugger, og får således ikke m_AdplusPath med, og der har vi altså problemet.
Nu skal jeg bare vente på, at Microsoft får rettet fejlen.