SEH exceptions on Windows CE (ARM)
Écrit par Vincent Richomme   
19-12-2008
I - Introduction

 

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):

 

FuncStart

PrologLen

FuncLen

ThirtyTwoBit

ExceptionFlag

Comment

00011008

0x4

0x15

1

1

wmain()

0001105c

0x1

0x9

1

0

filter_func()

000110a0

0x2

0x93

1

0

crtstart_ParseArgsWW

000112f4

0x4

0x30

1

1

mainWCRTStartup()

 

 

 

 

 

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 {
     ULONG Count; // number of items
     struct
     {
         ULONG BeginAddress;          
         ULONG EndAddress;              
         ULONG HandlerAddress;     
         ULONG JumpTarget;              
     } ScopeRecord[1];
 } SCOPE_TABLE, *PSCOPE_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.

 

FuncStart

PrologLen

FuncLen

ThirtyTwoBit

ExceptionFlag

Comment

00011008

0x4

0x15

1

1

wmain()

 

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 >