WHAT A SMALLTALK STACK LOOKS LIKE
The Smalltalk execution stack is made up of 32-bit pointers. These pointers contain an offset word followed by a segment word. Each method that is executed builds a stack frame which is a group of about 5 to 10 pointers. Every stack frame contains a 2-word frame link which is a pointer to the next stack frame. This frame link actually has the format of a SmallInteger "pointer" (i.e., 'integer segment'). Its segment value is the SmallInteger segment (which varies at runtime) while its "small integer value" is actually the offset into the stack of the start of the next frame.
Segments that are allocated to Smalltalk, when represented as hexadecimal, usually end in either 7 or F (5 or D in Windows 3.0). So in the Smalltalk execution stack, every other word ends in either 7 or F because the execution stack is one 32-bit pointer followed by another. When Smalltalk calls an API (which uses the same stack but is not written in Smalltalk), the stack no longer follows this pattern of contiguous 32-bit pointers.
The consistent pattern of a Smalltalk execution stack actually makes it easy to find Smalltalk frames after a crash in an API call. At the Codeview command line type:
dw ss:sp <return>
This will dump the stack in word format. The actual Smalltalk portion of the execution stack may be farther down the stack (look for the contiguous 'offset segment' pairs). To see more of the stack you have to type one or more times:
dw <return>
Here is an example of an actual crash, and how it is traced. What is typed is inside brackets [] .
[dw ss:sp]
817:6A10 B6DA 122F 012C 0001 0006 11C7 339A 1FC7 817:6A20 DEF0 26B8 6A3C 0000 001B 11C7 9914 1E97 817:6A30 5032 1F5F 012C 11C7 5032 1F5F 6A5C 11C7 817:6A40 9914 1E97 98EC 1E97 006D 11C7 11BF 11BF 817:6A50 3636 1FF7 98EC 1E97 82F0 1E97 6A94 11C7
The values on the left, 817:6AXX, are the addresses that are being dumped, and to the right are the contents. We see from the stack dump that the first line of contiguous 32-bit pointers is at offset 6A30. It should be noted that the segment values are not always in the even columns, as they are here; they may be in the odd columns. Always remember that the offset word for a given segment is on the left. We look for the first Smalltalk frame by searching for the frame link chain as described above. So, we begin looking at line 6A30 for an offset value slightly larger than 6A30. At the end of the line, at offset 6A3C, we find "6A5C 11C7". To verify that this is a frame link, look at address "6A5C 817" in the stack dump. That value is "6A94 11C7", which is another frame link. We can also see now that 11C7 is the SmallInteger segment. To verfify that 6A3C is the top of the Smalltalk execution stack, search upward in the dump from the link looking for a link to this address (6A3C). At offset 6A24, we find "6A3C 0000". While this points to the right place, it is not a Smalltalk frame link because the segment part of it is 0, instead of 11C7 like the others.
Now that we are convinced the Smalltalk execution stack starts at "817:6A3C" we can look at this frame. We consider the frame link pointer to be the first pointer in the frame. The third pointer in the frame, in this case "98EC 1E97", is to the compiled method. To find out what method it is, dump the pointer in word format.
[dw 1E97:98EC L10]
1E97:98EC 02F8 98EC 0000 0004 0007 5800 9914 1E97 1E97:98FC 11BF 11BF 3636 1FF7 027A 110F 51CA 1FE7
The pointer to the selector object is the 5th and 6th words on the second line. Dump this as ascii characters.
[da 110F:27A L30]
110F:027A ............Doit.. . .4 ........
The first 12 bytes constitute the object header, so the selector was Doit. If we wanted to know where this came from (i.e., who sent the message), we could repeat the process of dumping the method and the selector from the next frame in the stack, starting at "817:6A5C", and the next, until the stack base is reached. The base's frame link offset is 0.
In the example just described, an expression in the Transcript had been evaluated with a bad API call (this evaluation always creates a Doit method). Notice it does not tell us which API, so if there were multiple APIs in the same method, further investigation would be needed.
To find out what arguments were passed to the API call, we must find the return address back to Smalltalk. The offset is always around B68A (but can vary slightly with each release). The segment value always ends in 7 or F (5 or D), and is greater than 0900. Normally, it is less than 3000; in fact, it usually has a value in the 1000-1800 range. If we look at our sample stack, we can see that such a value is right on top of the stack. This is very unusual, but it just means that the crash occurred immediately after entering the API call. Deciphering the parameters is different every time, and you must know the types of the parameters to do it. In this case, the last evaluated expression had just one API call with just one argument, a USHORT. So we see that just before the return address the value 12C is what was passed to the API.
As another example, if your method was:
myMethod: int1 with: int2 with: aString <api: MYMETHOD ushort ushort struct none>
You would see a portion of the stack similar to:
B6DA 122F 9868 1347 0005 0003 0652 01C7
The arguments are pushed left to right, so this means the arguments are:
aString (passed as a struct, or pointer) 1347:9868 int2 (passed as a ushort) 0005 int1 (passed as a ushort) 0003