| SEH exceptions on Windows CE (ARM) |
| Écrit par Vincent Richomme | |||||||||||||||||||||||||||||||||||||||||||
| 19-12-2008 | |||||||||||||||||||||||||||||||||||||||||||
|
SEH exceptions have already been
described and one of the best article on this subject is still the one written
in 1997 by Matt Pietrek and available here :
http://www.microsoft.com/msj/0197/exception/exception.aspx
So why another article ? Simply
because SEH exceptions are implemented differently on architecture different
from x86. Indeed on x86 SEH exceptions relies on a x86 register to get address
of the Thread Information Bloc(TIB) where are stored SEH data.
On Windows CE, the most widespread
architecture for now is ARM so another mechanism is obviously necessary.
But before to go deeper let’s examine
a very simple example to see SEH exceptions in action.
Test.cpp
int _tmain(int argc,
_TCHAR* argv[])
{
DWORD*
ptr = NULL; //
NULL pointer
__try
{
puts("in
try");
*ptr = 0x4242; // causes an access violation exception;
}
__except(puts("in filter"),
EXCEPTION_EXECUTE_HANDLER){
puts("in
except");
}
puts("world");
return 0;
}
So basically SHE exceptions is based on specific
keywords implemented by MS compiler : __try and
__except.
To understand what is going on we have no choice but to
disassemble:
Ø
C:\Program Files\Microsoft Visual Studio 9.0\VC\bin\vcvars32.bat
Ø
Dumpbin /ALL /DISASM /OUT: TestSEH_Arm.txt
TestSEH_Arm.exe
Don’t be afraid even if you don’t understand anything
to assembly I have highlighted assembly and corresponding source code.
Let’s consider actually that the NULL pointer is
replaced by a valid one and follow what’s is going on if no exception occurs.
As you can see wmain starts at address 00011008 and is
followed by four instructions that corresponds to the function prolog.
Then you can see instructions corresponding to pointer
assignement then code inside __try { } keyword is executed. At 00011030 address
you can see
b 0001103C that is a jump to 0001103C , just after the
except scope.
00011000: 00011090 pHandler
00011004: 000120DC pHandlerData
wmain:
00011008: E1A0C00D mov r12, sp
0001100C: E92D5810 stmdb sp!, {r4, r11, r12, lr}
00011010: E28DB010 add r11, sp, #0x10
00011014: E24DD004 sub sp, sp, #4
00011018: E3A04000 mov r4, #0
0001101C: E59F0058 ldr
r0, [pc, #0x58] ; r0 = “in try”
00011020:
EB000016 bl puts
00011024:
E3A03C42 mov r3, #0x42, 24
00011028:
E3833042 orr r3, r3, #0x42
0001102C:
E5843000 str r3, [r4]
00011030:
EA000001 b 0001103C
00011034: E59F003C ldr r0,
[pc, #0x3C]; r0 = “in except”
00011038:
EB000010 bl puts
0001103C: E59F0030 ldr r0, [pc, #0x30] ; r0 = “in world”
00011040: EB00000E bl puts
00011044: E3A00000 mov r0, #0
00011048: E24BD010 sub sp, r11, #0x10
0001104C: E89DA810 ldmia sp, {r4, r11, sp, pc}
00011050: E51B0014 ldr r0, [r11, #-0x14]
00011054: E24BD010 sub sp, r11, #0x10
00011058: E89DA810 ldmia sp, {r4, r11, sp, pc}
0001105C: E52DE004 str lr, [sp, #-4]!
00011060: E59F0008
ldr r0, [pc, #8]; r0 = “in filter”
00011064:
EB000005 bl puts
00011068:
E3A00001 mov r0, #1; r0= EXCEPTION_EXECUTE_HANDLER
0001106C:
E49DF004 ldr pc, [sp], #4
00011070: 00012030 points to “in filter”
00011074: 0001201C points to “world”
00011078: 00012024 points to “in except”
0001107C: 0001203C points to “in try”
puts:
00011080: E59FC004 ldr r12, [pc, #4]
00011084: E59CC000 ldr r12, [r12]
00011088: E12FFF1C bx r12
0001108C: 00013000 andeq r3, r1, r0
…
__C_specific_handler:
00011090: E59FC004 ldr
r12, [pc, #4]
00011094: E59CC000 ldr r12, [r12]
00011098: E12FFF1C bx r12
0001109C: 00013004 andeq r3, r1, r4
crtstart_ParseArgsWW:
000110A0:
E92D4FF0 stmdb sp!, {r4 - r11, lr}
…
So when no
exception is raised the sample code could be rewritten like this :
// Example when no exception is raised:
int wmain(int argc,
_TCHAR* argv[])
{
DWORD*
ptr = SOME_VALID_VALUE;
puts("in
try");
*ptr
= 0x4242;
goto AEB; //AEB
means After Except Block
puts("in except");
AEB: //
puts("world");
return 0;
}
int filter_func()
{
puts("in filter");
return EXCEPTION_EXECUTE_HANDLER
}
NOTE :
Instructions inside __except( …) are compiled as a function (I named it filter_func
) by the compiler.
Now the
question is when an exception occurs where does the OS knows where to go ?
The partial answer
can be found in the .pdata section where an array of IMAGE_CE_RUNTIME_FUNCTION_ENTRY
(also sometimes named PDATA) is declared.
typedef struct
_IMAGE_CE_RUNTIME_FUNCTION_ENTRY {
unsigned int
FuncStart : 32;
unsigned int
PrologLen : 8;
unsigned int FuncLen
: 22;
unsigned int
ThirtyTwoBit : 1;
unsigned int
ExceptionFlag : 1;
} IMAGE_CE_RUNTIME_FUNCTION_ENTRY, *PIMAGE_CE_RUNTIME_FUNCTION_ENTRY;
Here is the
raw data from .pdata section :
RAW DATA #4
00014000: 08 10 01 00 04 15 00 C0 5C 10 01 00
01 09 00 40 .......À\......@
00014010: A0 10 01 00 02 93 00 40 F4 12 01 00
04 30 00 C0 ......@ô....0.À
00014020: B4 13 01 00 01 0B 00 40 E0 13 01 00
01 1B 00 40 ´......@à......@
00014030: 4C 14 01 00 01 41 00 40 50 15 01 00
01 06 00 40
Cet e-mail est protégé contre les robots collecteurs de mails, votre navigateur doit accepter le Javascript pour le voir
@
00014040: 7C 15 01 00 02 0E 00 40 C0 15 01 00
01 14 00 40 |......@À......@
Now let’s try
to decode it and since I am a bit lazy I will write a small application and let
the compiler decode the first 4 entries for us :
int _tmain(int argc, _TCHAR* argv[])
{
BYTE hex[][8]
=
{
{ 0x08, 0x10, 0x01, 0x00, 0x04,
0x15, 0x00, 0xC0 },
{ 0x5C, 0x10, 0x01, 0x00, 0x01,
0x09, 0x00, 0x40 },
{ 0xA0, 0x10, 0x01, 0x00, 0x02,
0x93, 0x00, 0x40 },
{ 0xF4, 0x12, 0x01,
0x00, 0x04, 0x30, 0x00, 0xC0 },
};
for (int i = 0; i < sizeof(hex)
/ sizeof(hex[0]); i++)
{
memcpy(&imgCeFuncEntry, hex[i], (8));
printf("PDATA[%d]\n", i);
printf("FuncStart : %.8x\n",
imgCeFuncEntry.FuncStart);
printf("PrologLen : 0x%x\n",
imgCeFuncEntry.PrologLen);
printf("FuncLen : 0x%x\n",
imgCeFuncEntry.FuncLen);
printf("ThirtyTwoBit : %d\n",
imgCeFuncEntry.ThirtyTwoBit);
printf("ExceptionFlag : %d\n",
imgCeFuncEntry.ExceptionFlag);
}
return 0;
}
Here is the interpretation
of .pdata section (only first 4 entries are shown):
So if we read
the first line we discover that a function starts at address 0x11008 with a
prolog of 4 instructions and a length of 0x15 = 21 instructions. ThirtyTwoBit
field indicates that each instruction are coded on 4 bytes (ARM) .
Finally the
last field ExceptionFlag is set and indicates that this function has an
exception handler.
Until now
everything’s fine but some questions are still pending even if we saw that some
information is stored in the .pdata section it doesn’t seem enough to handle
exceptions and you are right.
The solution
is in this ExceptionFlag field, MSDN says that if this bit is set we must find
a PDATA_EH structure just before the function :
struct
PDATA_EH { unsigned int* pHandler; unsigned int* pHandlerData;};
So if we look again just before
function start address 0x11008
we will see two instructions that in fact are addresses.
00011000: 00011090 pHandler
00011004: 000120DC pHandlerData
wmain:
00011008: E1A0C00D mov r12, sp
The first field named pHandler points to 00011090 address and corresponds
to the generic exception handler __C_specific_handler. This function is
responsible to find out what to do with the exception. It does this by parsing
an array of SCOPE_TABLE
structures defined like this :
typedef
struct _SCOPE_TABLE {
and this
array is located at address given by pHandlerData ie 000120DC in this
example.
When we look
at this address we see that it’s actually located in the read only data section
called .rdata :
SECTION
.rdata :
000120D0: 45 48 5F 41 72 6D 2E 70 64 62 00 00
01 00 00 00 EH_Arm.pdb......
000120E0: 1C 10 01 00 30 10 01 00 5C 10 01 00 34 10 01 00
....0...\...4...
000120F0: 01 00 00 00 1C 13 01 00 90 13 01 00
B4 13 01 00 ............´...
00012100: 94 13 01 00 2C 21 00 00 00 00 00 00
00 00 00 00 ....,!..........
00012110: 5C 21 00 00 00 30 00 00 00 00 00 00
00 00 00 00 \!...0..........
00012120: 00 00 00 00 00 00 00 00 00 00 00 00
53 04 00 80 ............S...
00012130: 57 00 00 80 C1 00 00 80 21 00 00 80
14 04 00 80 W...Á...!.......
00012140: 3F 00 00 80 6D 06 00 80 19 02 00 80
24 00 00 80 ?...m.......$...
00012150: 54 07 00 80 53 07 00 80 00 00 00 00
43 4F 52 45 T...S.......CORE
00012160: 44 4C 4C 2E 64 6C 6C 00 DLL.dll.
120dc: 00000001 -> Count
120e0: 0001101c -> BeginAddress // begin of __try block
120e4: 00011030 -> EndAddress //end of __try
block
120e8: 0001105c -> HandlerAddress // exception filter
120ec: 00011034 -> JumpTarget // code inside __except
Finally we
have everything to understand SEH exception mechanism and we could suppose that
the following scenario happens:
CPU is executing code
Exception is raised
Program Flow is passed to OS exception trap
OS looks at current Program Counter(PC) to know
at what address the exception is.
In our example exception happens when we are deferencing our NULL pointer(R4)
at address 0001102C:
0001102C: E5843000 str r3, [r4] // store value of register R3 in
memory value pointed by R4
Then OS
decides to look at .pdata section and parse our array of IMAGE_CE_RUNTIME_FUNCTION_ENTRY
to see if it can handle it.
So 0001102C
is after FuncStart (0x11008) and before
FuncStart + FuncLen(0x 1101D) and we
have a handler for this exception (ExceptionFlag = 1). So OS looks at instructions before function start and can call the generic exception handler __C_specific_handler that will look into SCOPE_TABLE array pointed by pHandlerData to see what to do.
|
|||||||||||||||||||||||||||||||||||||||||||
| < Précédent | Suivant > |
|---|

