A: background

1. Tell a story

This article originated from the wonderful article WINDBG positioning ASP. NET MVC project abnormal collapse source location, write very good, but the fly in the ointment is that after reading the full text, always feel that there is not enough, is not to throw the parameters before the anomaly to find out… This article I will try to make up for this regret 😁.

The application crashes on IIS, and there is no catch. There is only one AccessViolationException in the Windows log. How do you analyze this?

To be honest, this is the first time I have encountered such an exception in a managed language, C#. It is quite strange.

If you have a dump + AccessViolationException, you can still dump it. If you have a dump + AccessViolationException, you can also dump it.

2. WINDBG analysis

1. The thread that looks for exceptions

If you catch a dump at the time of an exception crash, the exception will generally hang on the thread of execution.

0:0:03 7 >! t ThreadCount: 9 UnstartedThread: 0 BackgroundThread: 9 PendingThread: 0 DeadThread: 0 Hosted Runtime: no Lock ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception 8 1 2188 019da830 28220 Preemptive 10C08398:00000000 01a02bd8 0 Ukn 29 2 36b8 025d7738 2b220 Preemptive 00000000:00000000 01a02bd8 0 MTA (Finalizer) 31 3 1c6c 0260b568 102a220 Preemptive 00000000:00000000 01a02bd8 0 MTA (Threadpool Worker) 32 4 315c 02616678 21220 Preemptive 00000000:00000000 01a02bd8 0 Ukn 34 6 31c0 026180e0 1020220 Preemptive 00000000:00000000 01a02bd8 0 Ukn (Threadpool Worker) 35 7 1274 02618628 1029220 Preemptive 069745A0:00000000 01a02bd8 0 MTA (Threadpool Worker) 37 8 2484  02617108 1029220 Preemptive 0EBFFB18:00000000 01a02bd8 0 MTA (Threadpool Worker) System.AccessViolationException 0ebee9dc 38 9 2234 026156a0 1029220 Preemptive 0AAED5CC:00000000 01a02bd8 0 MTA (Threadpool Worker) 39 10 3858 02617b98 1029220 Preemptive 0CB7BEE0:00000000 01a02bd8 0 MTA (Threadpool Worker)

The above no. 37 thread clear abnormal System. The records of AccessViolationException, also with an exception object behind the address 0 ebee9dc, you can use! Do is printed out.

0:0:03 7 >! do 0ebee9dc Name: System.AccessViolationException MethodTable: 6fc1bf4c EEClass: 6f926bec Size: 96(0x60) bytes File: C:\Windows\Microsoft.Net \ assembly \ GAC_32 \ mscorlib \ v4.0 _4. 0.0.0 __b77a5c561934e089 \ mscorlib DLL Fields: MT Field Offset Type VT Attr Value Name 6fc146a4 4000005 10 System.String 0 instance 0ebf02f0 _message 6fc1be98 4000006 14... tions.IDictionary 0 instance 00000000 _data 6fc146a4 400000c 2c System.String 0 instance 0ebfd24c _remoteStackTraceString

This Exception has a number of properties, such as the _remoteStackTraceString on the last line, which shows the Exception stack information.

0:0:03 7 >! do 0ebfd24c Name: System.String MethodTable: 6fc146a4 EEClass: 6f8138f0 Size: 10444(0x28cc) bytes File: C:\Windows\Microsoft.Net \ assembly \ GAC_32 \ mscorlib \ v4.0 _4. 0.0.0 __b77a5c561934e089 \ mscorlib DLL String: In System.Data.Com, mon. UnsafeNativeMethods. ICommandText. Execute (IntPtr pUnkOuter, Guid & riid, tagDBPARAMS pDBParams, IntPtr& pcRowsAffected, The Object & ppRowset) in the System. Data. The OleDb. OleDbCommand. ExecuteCommandTextForMultpleResults (tagDBPARAMS dbParams, The Object & executeResult) in the System. Data. The OleDb. OleDbCommand. ExecuteCommandText (Object & executeResult) in System.Data.OleDb.OleDbCommand.ExecuteCommand(CommandBehavior behavior, The Object & executeResult) in the System. Data. The OleDb. OleDbCommand. ExecuteReaderInternal (CommandBehavior behaviors, String method) in the System. Data. The OleDb. OleDbCommand. ExecuteNonQuery () in the XXX. Model. XXX. GetOneData (OleDbCommand comm) in xxx.Model.xxx.getOtherDataSource(List`1 keys, Data Dictionary ` 2) on the XXX. Controllers. XxxOtherController. Post (JObject json) System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.<>c__DisplayClass10.<GetExecutor>b__9(Object instance, Object [] methodParameters) in the System. Web. Http. Controllers. ReflectedHttpActionDescriptor. ActionExecutor. Execute (Object instance, The arguments Object []) in the System. Web. Http. Controllers. ReflectedHttpActionDescriptor. ExecuteAsync (HttpControllerContext controllerContext, IDictionary`2 arguments, CancellationToken cancellationToken)

An AccessViolationException is thrown when executing a database. What kind of operation is it that makes this exception possible? Okay, so I’m going to dig up what does getOneData() do?

2. Find the problem code getOneData()

To find the source code for getOneData(), as usual, use! name2ee + ! Savemodule export.

0:0:03 7 >! name2ee *! xxx.Model.xxx.getOneData -------------------------------------- Module: 1b9679c0 Assembly: xxx.dll Token: 06000813 MethodDesc: 0149faec Name: xxx.Model.xxx.getOneData(System.Data.OleDb.OleDbCommand) JITTED Code Address: 1ede0050 -------------------------------------- 0:0:037> ! savemodule 1b9679c0 E:\dumps\2.dll 3 sections in file section 0 - VA=2000, VASize=d8d74, FileAddr=200, FileSize=d8e00 section 1 - VA=dc000, VASize=318, FileAddr=d9000, FileSize=400 section 2 - VA=de000, VASize=c, FileAddr=d9400, FileSize=200

With 2.dll, you can then use ILSPY to take a look at the source code.

From the source code is also some of the normal operation, no special place, since there is no problem in writing, I can only suspect that there is a problem with some data, next ready to dig a dig OleDbCommand.

3. Extract the OleDBCommand object from the thread stack

If you’ve played ADO.NET, you’ll know that the final SQL + parameters are hidden on OledbCommand. See the code below:

public sealed class OleDbCommand : DbCommand, ICloneable, IDbCommand, IDisposable { public override string CommandText { get; set; } public new OleDbParameterCollection Parameters { get { OleDbParameterCollection oleDbParameterCollection = _parameters; if (oleDbParameterCollection == null) { oleDbParameterCollection = (_parameters = new OleDbParameterCollection()); } return oleDbParameterCollection; }}}

So the goal is very clear, is the commandText + Parameters to dig out, say dry dry, with! Clrstack-a extracts all the parameters on the thread stack, as shown in the figure below:

Because the exception was thrown and the thread call stack was destroyed, the local variables + method arguments on the call stack were destroyed. Really want to cry 😭 niu.

OledBCommand is a reference to the stack, and the stack address is lost. OledBCommand must still be on the hot Gen0. After all, it’s just thrown an exception. Definitely not recycled, haha, suddenly full of energy again.

4. Find OLEDBCOMMAND from the managed heap

To find OleDbCommand on the managed heap, use the following command:! Dumpheap-type OleDbCommand is sufficient.

| | 0:0:03 7 >! dumpheap -type OleDbCommand Address MT Size 02a8393c 6c74a6a8 84 02bc280c 6c74a6a8 84 02bd98dc 6c74a6a8 84 02be1d74 6c74a6a8 84 02be3c68 6c74a6a8 84 02be5b3c 6c74a6a8 84 0696f978 6c74a6a8 84 0a94ea54 6c74a6a8 84 0a9678b8 6c74a6a8 84 0a96a5a0 6c74a6a8 84 0aabefe4 6c74a6a8 84 0eb10e08 6c74a6a8 84 Statistics: MT Count TotalSize Class Name 6c74a6a8 12 1008 System.Data.OleDb.OleDbCommand Total 12 objects

There are only 12 OleDbCommand in the managed heap, which means that the program died within two circles. The next thing to do is to check whether there is any exception in the SQL + Parameter one by one. The human body can check it, which can blind the eyes. So I had to leave the dirty work to script, so I spent an hour writing a script, almost fell asleep 😪.

"use strict"; function initializeScript() { return [new host.apiVersionSupport(1, 7)]; } function InvokeScript () {var output = exec("! dumpheap -type System.Data.OleDb.OleDbCommand -short"); for (var line of output) { showOleDb(line); log("------------------------------------------------------------------------"); }} function showOleDB (oleDB) {log(" oleDB: "+ oleDB); showsql(oledb); showparameters(oledb); } //show sql function showsql(oledb) { var command = "! do -nofields poi(" + oledb + "+0x10)"; var output = exec(command).Skip(5); for (var line of output) { log(line); } } //show parameters function showparameters(oledb) { var address = "poi(poi(poi(" + oledb + "+0x1c)+0x8)+0x4)" var arrlen = "poi(" + address + "+0x4)"; var command = "! da -nofields -details " + address; //var str = ""; var output = exec(command).Where(k => k.indexOf("[") == 0).Select(k => k.split(' ')[1]) .Where(k => k != "null").Select(k => k); for (var line of output) { var name = showparamname(line); var value = showparamvalue(line); log(name + " -> " + value); } } //show parametername function showparamname(param) { var command = "!do -nofields poi(" + param + "+0xc)"; var output = exec(command); output = output.Skip(5).First().replace("String: ", "");} //show paramterValue function showParamValue (param, offset) { Var address = "poi(" + param + "+0x14)"; var isGtZero = ParseInt (exec(".printf \"%d\"," + address).first ()) > 0; if (! IsGtZero) return "0"; var command = "! Do-nofields "+ address; var output = exec(command); System.dateTime var isDateTime = output.first ().indexOf(" System.dateTime ") >-1; if (isDateTime) return ().indexOf(" System.dateTime ") >-1 getFormatDate(address); output = output.Skip(5).First().replace("String: ", ""); return output; } function getFormatDate(address) { //16hex var dtstr = ".printf \"%02X%02X\",poi(" + address + "+0x8),poi(" + address + "+0x4);"; //10hex var num = parseInt("0x" + exec(dtstr).First(), 16); var command = "!filetime ((0n" + num + " & 0x3fffffffffffffff) - 0n504911519999995142)"; var time = exec(command).First().split("(")[0].trim(); return time; } function log(instr) { host.diagnostics.debugLog("\n" + instr + "\n"); } function exec(str) { return host.namespace.Debugger.Utility.Control.ExecuteCommand(str); }

In short, the poi above means to take the value of the address, which may be a number or a reference address, and then run the script, because the information is too sensitive, it can only be virtualized.

------------------------------------------------------------------------ oledb: 0eb10e08 String: update xxx set a=:a, b=:b, c=:c where info_id = :info_id a -> 'xxx' b -> 'yyy' c -> File: C:\Windows\Microsoft.NET \ Framework \ v4.0.30319 \ Temporary ASP.NET Files\collegeappxy\e05a2cb1\4405de9e\assembly\dl3\d914f432\c1375f08_c05cd201\Newtonsoft.Json.dll info_id -> 1

SQL = newtonsof. json. DLL File = newtonsof. json. DLL File = newtonsof. DLL File;

| | 0:0:03 7 >! do -nofields poi(0eb9ba40+0x14) Name: Newtonsoft.Json.Linq.JObject MethodTable: 1c600d98 EEClass: 1c5f31d0 CCW: 1bbd0020 Size: 68(0x44) bytes File: C:\Windows\Microsoft.NET \ Framework \ v4.0.30319 \ Temporary ASP.NET Files\collegeappxy\e05a2cb1\4405de9e\assembly\dl3\d914f432\c1375f08_c05cd201\Newtonsoft.Json.dll

The exception is caused by the parameterization of the JObject. I also look up the JObject, which is formatted after toString (), as shown in the figure below:

If you want to remove this formatting, you need to add an enumeration of None to toString ().

Three:

In general, I think this is a bug of OleDbCommand, since it is to do parameterization, even if I put 💩 in, you still need to give me the correct library, right? Secondly, from the analysis results, know this abnormal call stack, it is very easy to solve, use the log record of OLEDBCOMMAND at that time can be, use script violence search that is the last thing 😓 send, finally thanks to the brick team big man’s wonderful article and dump.

For more high-quality dry goods: see my GitHub:dotnetfly