Reading The Debug Reports

This forum is for discussions on how to edit what can not be edited through the txt files, needless to say this isn't about battle net hacking.

Moderators: Nefarius, Havvoric

Locked
User avatar
Myhrginoc
Retired Admin
Cherub
Posts: 12062
Joined: Sat May 25, 2002 7:28 am
Location: Percussion U

Hand-picked

Reading The Debug Reports

Post by Myhrginoc » Mon Mar 10, 2003 4:46 am

One of the most difficult things to do in the modding business is interpret what went wrong when you crash. Fortunately, Diablo II is equipped with helpful diagnostics to narrow down root causes. Cryptic though the messages may be, try to imagine finding what happened without any feedback at all! I have written this introduction to the debug report so you can extract information that might help you track down the error.

Assertion Errors
These are the easy ones, because the message gives you a hint. The assertion error has two lines: the first line identifies the module where the error occurred, and the second line gives you the condition that should have been true for no error to occur. The line number in the first line does not pertain to anything that we have access to, but where the same message is used several times the line number distinguishes between the uses. An assertion error occurs because the programmers knew of a dangerous condition and trapped the code for it.

Unhandled Exceptions
Where the programmers did not think of cases to test, and execution goes out of bounds, you will get a Windows error instead. The most common type of unhandled exception is ther c0000005 Access Violation error. This error happens when the code tries to access a block of memory it is not permitted to access, as regulated by the operating system itself. The most common cause of an unhandled exception is a dereferenced pointer that gets based from address 00000000 instead of the address it was supposed to receive.

Structure of A Debug Report
Debug reports can be found in your Diablo II install directory. They are named D2yymmdd.txt, where yy is the year, mm is the month and dd the day the report was generated. Any time you run D2 you generate a debug report, whether anything goes wrong or not. Under normal execution, the report contains information about your machine and what the game did when it started up and when it shut down. We will ignore these sections in this discussion.

There are several parts to a debug report that are added when an error occurs. The first section you can think of as a header, it contains the assertion or unhandled exception notice you see in the popup at error time. Below the notice there may be register listings, these are the contents of the CPU registers at the time of the error. Here are a couple of examples:

Code: Select all

15:16:58.843  --------  FILE: urce\D2Common\UNITS\UnitRoom.cpp    LINE: 23  --------

Assertion Failure
Location : urce\D2Common\UNITS\UnitRoom.cpp, line #23   <--- module in error
Expression : DungeonTestRoomGame ( hRoom, nX, nY)       <--- violated condition

Code: Select all

20:10:41.986  ***** UNHANDLED EXCEPTION: ACCESS_VIOLATION (c0000005)
20:10:41.986  Fault address:  6FC5B038 01:0002A038 D:\Diablo_II\D2Game.dll
20:10:41.986  eax:00000001 ebx:05c4163c ecx:00000000 edx:05c41600 esi:00000242
20:10:41.986  edi:00000000 ebp:00000000 esp:0012ecd4 eip:6fc5b038 flg:00010202
20:10:41.986  cs:001b ds:0023 es:0023 ss:0023 fs:003b gs:0000
The most important register for locating the fault is the Instruction Pointer (EIP), this contains the address where execution failed. The other registers are useful if you have a debugger running in the background or have a listing of the disassembled module, then you can backtrack and see what instructions were doing what to get those values. This is of limited utility, however, since most of the program's information is external to the registers. If you don't get a register listing in your header (as in the first example), do not despair! You can find it either in the stack dumps or by doing some sleuthing with a debugger. The strings used in the assertion message are themselves data that can be searched within the module, and once you know where it is referenced you can find the assertion code (and the function that uses it). Assertion code come in two parts (the test and the report), and appears as below. This example is from d2common.10384, and is used to test for the four corners of a map section (hence "hRoom, nX, nY"):

Code: Select all

6FDAD437  |.  56                PUSH ESI
6FDAD438  |.  E8 C3060000       CALL D2Common.#10326
6FDAD43D  |.  56                PUSH ESI
6FDAD43E  |.  8BD8              MOV EBX,EAX
6FDAD440  |.  E8 2B080000       CALL D2Common.#10329
6FDAD445  |.  8B0F              MOV ECX,DWORD PTR DS:[EDI]
6FDAD447  |.  3BD9              CMP EBX,ECX                   <--- test upper Y
6FDAD449  |.  0F8C 07010000     JL D2Common.6FDAD556          <--- fail
6FDAD44F  |.  8B57 08           MOV EDX,DWORD PTR DS:[EDI+8]
6FDAD452  |.  03D1              ADD EDX,ECX
6FDAD454  |.  3BDA              CMP EBX,EDX                   <--- test lower Y
6FDAD456  |.  0F8D FA000000     JGE D2Common.6FDAD556         <--- fail
6FDAD45C  |.  8B4F 04           MOV ECX,DWORD PTR DS:[EDI+4]
6FDAD45F  |.  3BC1              CMP EAX,ECX                   <--- test left X
6FDAD461  |.  0F8C EF00000      JL D2Common.6FDAD556          <--- fail
6FDAD467  |.  8B57 0C           MOV EDX,DWORD PTR DS:[EDI+C]
6FDAD46A  |.  03D1              ADD EDX,ECX
6FDAD46C  |.  3BC2              CMP EAX,EDX                   <--- test right X
6FDAD46E  |.  0F8D E2000000     JGE D2Common.6FDAD556         <--- fail

Code: Select all

6FDAD556  |>  6A 17             PUSH 17                  <--- the line number you see
6FDAD558  |.  68 34E2DD6F       PUSH D2Common.6FDDE234   <--- location of first string
;  ASCII "C:\Src\Diablo2\Source\D2Common\UNITS\UnitRoom.cpp"
6FDAD55D  |.  68 D0E1DD6F       PUSH D2Common.6FDDE1D0   <--- location of second string
;  ASCII "DungeonTestRoomGame ( hRoom, nX, nY)"
6FDAD562  |.  E8 1B650000       CALL <JMP.&Fog.#10023>   <--- sets up the popup
6FDAD567  |.  83C4 0C           ADD ESP,0C
6FDAD56A  |.  6A FF             PUSH -1
6FDAD56C  |.  E8 3A660000       CALL D2Common.6FDB3BAB   <--- exit call (VC++ RTL)

Below the header is the stack dump. The stack is a section of memory used to store function parameters, return addresses, buffers and local variables. Function parameters are just as they would be in a high level language. The return address is so the CPU knows where to go back to once execution encounters the RETN or RETN XX instruction. Buffers are used to build output, such as converting "%s%s.TXT" into "DATA\GLOBAL\EXCEL\ITEMTYPES.TXT". And local variables are used only within the function. The important thing to remember about stack dumps is only the first part is likely to have any relationship to the error! Here is an example:

Code: Select all

20:10:42.033  0012ECD4: 0016C405 42020000 7C00EE01 94E2C56F  ..Ä.B...|.î..âÅo
              0012ECE4: 0CED1200 00000000 01000000 01000000  .í..............
              0012ECF4: 7C00EE01 7C00EE01 0016C405 95130000  |.î.|.î...Ä.....
              0012ED04: 0000EE01 4CCBCA6F 42020000 D6100000  ..î.LËÊoB...Ö...
That is rather ugly looking, no? Well, this error didn't have any helpful strings in it, such as the name of a DCC file that might have been involved in a display crash. But there are still some educated guesses to be made. The stack dump is grouped in dwords for us, and a dword in Intel-speak is always byte-order backwards. The first dword is at the stack pointer (ESP register), whether there is a register listing in the header or not. So here are some numbers with their possible meanings:

94E2C56F --> 6FC5E294 D2 module addresses are almost universally between 6F700000 and 70000000, so any time you see 6Fxxxxxx think "address". Now we need to know if it is code or data. From a memory map of D2 modules, you will see this is part of the memory block for d2game.dll code, so this is probably the return address for the function including the instruction at 6FC5B038. The return is always the instruction after the call that got you there, so it represents something that hasn't happened yet. But it is useful to know how you got into the function that crashed, and the return address is the only way to know where you came from if the function is called from several places.

7C00EE01 --> 01EE007C Diablo II operates on the client-server model, which is to say part of the code is acting as a game server and part as a game client. This is true even in single player. On the server side is an important dynamic structure known as ptGame, it contains data the game server uses to track all sorts of stuff. I have yet to see a ptGame that doesn't follow the pointer format 0xxx007C.

0016C405 --> 05C41600 There are other structures in D2 other than ptGame. One common structure is ptUnit, which in its various aspects contains dynamic data about characters, monsters, objects, missiles, items and tiles. These structures are aligned on one or more 256-byte memory pages, and they are assigned on the heap during gameplay, so they have the pointer format 0xxxxxx00.

xxxx0000 --> 0000xxxx Any number this small cannot be an address, that would put a pointer in forbidden territory. So these numbers are data on the stack, either as parameters or as local variables. Anything between the stack pointer and the first 6F-style address is in local variable memory. Anything above that first 6F-style address is possibly pushed parameters, buffer space, or left over from previous activity and unrelated to the error at hand.

Below the stack dump there may be a short excerpt from the function where the crash occurred, called code bytes. This is machine language, so you need to at least disassemble the code section to get a grasp of the logic. Unless you like to disassemble things by hand, you can ignore this. The only time the code bytes will be very useful is if you are debugging self-modifying code.

Next are the enumerated modules list and the loaded symbols list. All either of these do is list the executables that are visible to Diablo II when the game is running, and give you a memory map of where the DLLs ended up. But the memory map won't tell you which part is code and which part is data. You can ignore this, unless you suspect your crash occurred because a module wasn't loaded. Only mods using D2Extra or other custom DLLs are likely to show abnormal conditions in either section.

Code: Select all

20:10:42.346  Annotated stack dump

                     // EIP = 0x6FC5B038 - D2Game.dll - Ordinal10045+1A918

              0012ECD4: 0016C405 42020000 7C00EE01 94E2C56F  ..Ä.B...|.î..âÅo

                           // 0x6FC5E294 - D2Game.dll - Ordinal10045+1DB74

              0012ECE4: 0CED1200 00000000 01000000 01000000  .í..............
              0012ECF4: 7C00EE01 7C00EE01 0016C405 95130000  |.î.|.î...Ä.....
              0012ED04: 0000EE01 4CCBCA6F                    ..î.LËÊo

                           // 0x6FCACB4C - D2Game.dll - Ordinal10034+1BA0C

              0012ED0C: 42020000 D6100000 89130000 01000000  B...Ö...........
              0012ED1C: 88130000 40FA8A03 0016C405 00000000  ....@ú....Ä.....
              0012ED2C: 00F20501 06000000 80110000 88130000  .ò..............
              0012ED3C: A8110000 B0130000 7C00EE01 005FC405  ¨...°...|.î.._Ä.
              0012ED4C: 03000000 27000000 3C5FC405 81110000  ....'...<_Ä.....
              0012ED5C: 27000000 89130000 A60DC66F           '.......¦.Æo
Now we are at the annotated stack dump. This is a duplicate of the stack dump from before, but now with some breakouts for the various functions noted on the stack. However they need to be taken with a grain of salt. As mentioned earlier, you do not know if this is a single branch of the call tree or if something higher in the stack progressed past the current location of the stack pointer, returned back up and started down the new path. So only the first few blocks are likely to be helpful.

At the end is the stack crawl section, which is a summary of information you can get from the two stack dumps.

When you are reporting an error, don't include the entire file in one massive dump on the forums. The only parts that will be meaningful will be the header (with register listing if present), and the top lines of the two stack dumps (through the first 6F-style address and another five lines at most).
Last edited by Myhrginoc on Sat Apr 26, 2003 4:56 pm, edited 6 times in total.
Do the right thing. It will gratify some people and astonish the rest.
~ Mark Twain
Run Diablo II in any version for mods: tutorial
The Terms of Service!! Know them, abide by them, and enjoy the forums at peace.
The Beginner's Guide v1.4: (MS Word | PDF) || Mod Running Scripts || TFW: Awakening

User avatar
Myhrginoc
Retired Admin
Cherub
Posts: 12062
Joined: Sat May 25, 2002 7:28 am
Location: Percussion U

Hand-picked

Re: Reading The Debug Reports

Post by Myhrginoc » Sat Mar 22, 2003 9:09 pm

One note about stack dumps and assertion errors. The top referenced function will probably be Fog.10028, and the next one down would then be Fog.10023. Those are both error reporting functions and have nothing to do with the actual error itself. (There are other Fog.1002x functions you might see listed, but those are rare in comparison.) You have to look at the stack immediately above these two references to find useful data.
Last edited by Myhrginoc on Sat Mar 22, 2003 9:12 pm, edited 1 time in total.
Do the right thing. It will gratify some people and astonish the rest.
~ Mark Twain
Run Diablo II in any version for mods: tutorial
The Terms of Service!! Know them, abide by them, and enjoy the forums at peace.
The Beginner's Guide v1.4: (MS Word | PDF) || Mod Running Scripts || TFW: Awakening

User avatar
Myhrginoc
Retired Admin
Cherub
Posts: 12062
Joined: Sat May 25, 2002 7:28 am
Location: Percussion U

Hand-picked

Re: Reading The Debug Reports

Post by Myhrginoc » Tue Jan 27, 2004 8:06 am

The above information still holds true for version 1.10x. But there is a new wrinkle added because Blizzard seriously lengthened d2game.dll, and the module now extends into d2common.dll's default memory mapping. One of the two modules must lose and get relocated. In the first beta it was d2game that lost, but in the second beta and final versions it is d2common that loses. So the old fact that all code addresses are in the form 6Fxxxxxx is no longer always true.

One solution is to use the rebased d2common available from the File Center. This version of d2common is identical to the Blizzard release in all particulars except for the default memory mapping changing from 6FD4xxxx to 6F60xxxx. There is no longer any collision between modules so relocations won't happen unless something else is brought into the Diablo II process. (I have used the rebased d2common many times and never had a crash due to rebasing. It is also compatible with D2Extra and other custom modules.)

Without rebasing, you need to make note of d2common's load address each time you start the first game of your session. (Once you have loaded d2common into the process, it will stay put no matter how many games you start, as long as you never crash or shut down the entire program.) Because relocation is a Windows function and depends on individual setups and running modules, you cannot count on d2common ending up in the same place each time.

Whether you use the rebased d2common or examine a relocation, you need to know how to convert addresses between what you see in the debug environment or from the report, and what listings you might read in these forums. The default loading address is 6FD4xxxx, so you need to subtract the difference from our listings. If an actual location is based on the rebased d2common, the delta is hex 740000, since 6FD4 - 6F60 = 74. If the listing is based on a relocation, take the first four digits of a current address and subtract from 6FD4, then add four zeros. Put Windows Calculator in scientific view, with radio buttons to hex numbers and dword width; it is an excellent tool for these calculations.
Last edited by Myhrginoc on Tue Jan 27, 2004 8:08 am, edited 1 time in total.
Do the right thing. It will gratify some people and astonish the rest.
~ Mark Twain
Run Diablo II in any version for mods: tutorial
The Terms of Service!! Know them, abide by them, and enjoy the forums at peace.
The Beginner's Guide v1.4: (MS Word | PDF) || Mod Running Scripts || TFW: Awakening

Locked

Return to “Code Editing”