March 28, 2024, 10:42:30 PM

News:

Own IWBasic 2.x ? -----> Get your free upgrade to 3.x now.........


Exceptions handling

Started by sapero, November 08, 2006, 04:31:26 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

sapero

November 08, 2006, 04:31:26 AM Last Edit: November 08, 2006, 12:34:54 PM by sapero
I've created two small fuctions for easier exceptions catching and handling. It uses the standard SEH based architecture, where in register FS is a pointer to some structure, at offset 0 is a pointer to EXCEPTION_REGISTRATION linked list (or -1):
struct EXCEPTION_REGISTRATION
{
ÂÃ,  ÂÃ, EXCEPTION_REGISTRATION *next;
}

Every time an exception occurs, the system loads the value from [FS:0] for current thread and checks it for a valid pointer.
If the value is -1 here is no exception handler, or the last node has been reached.

We can extend this structure by adding custom data like registers ESP and EBP (to be able to continue execution), or other usefull data like pointer to a laber where to continue after exception.
Right after 'next' member is a pointer to current exception handler. All other is optional, user defined.
I have added ESP,EBP and resume label to this structure, using pure assembly code ofc.

The usage is very near C++ exceptions handling: try and catch

if __try()
{
// paste here any unsafe code.
}
// point A

if __catch(&ExceptionCode, &ExceptionAddress, &pExceptionDescription)
{
// show a message - exception occured
}
else
{
// here was no exception inside __try block
}

The __try function appends new node to the EXCEPTION_REGISTRATION structure (as the first) and automatically saves pointer to code marked as point A, also outside the first IF instruction, then jumps directly inside IF.
This function returns TRUE, but your IF is not executed

Note to Paul:
if __try()
{
}
is assembled (and should be):
call __try
and eax,eaxÂÃ,  ÂÃ,  // 2 bytes
jz near label_if_not // 2 bytes + 4 bytes offset


The __try function reads the offset in 'jz near' and uses it to calculate code pointer outside IF.

Every call to __try MUST be matched by a call to __catch, to restore original stack pointer, and remove the node form thread linked list. Before it is removed, a validation is performed.
You can call __try inside other __try...
if __try()
{
// paste here any unsafe code.
if __try() {/*unsafe code*/}
// after exception, or success
if __catch(&ExceptionCode, &ExceptionAddress, &pszExceptionDescription)
{/*exception!*/} else {/*ok*/}
}
// after exception, or success
if __catch(&ExceptionCode, &ExceptionAddress, &pExceptionDescription)
{
// show a message - exception occured
}
else
{
// here was no exception inside __try block
}

Let's go with example code:
// #use "gp.lib"
extern int __try(); // returns always TRUE, use it with: if __try() {dangerous code}
extern int __catch(int *ppExceptionCode,int *ppExceptionAddress,string *ppExceptionDescription); // returns TRUE only after exception

sub main()
{
unsigned int ExceptionCode,ExceptionAddress;
string *pExceptionDescription;

// DEMO 1: generate Privileged Instruction exception
if __try()
{
// paste here any unsafe code
print("executing al = in(LPT1) ...");
#emit mov dx,888
#emit in al,dx
}
// -> here <- your code continues after exception.
// Do safe coding only, before __catch() !!

// always call __catch after __try to cleanup stack ...
// and restore previous exceptions handler
if __catch(&ExceptionCode, &ExceptionAddress, &pExceptionDescription)
{
ShowException(ExceptionCode, ExceptionAddress, pExceptionDescription);
}
else
{
print("port accessed successfully! Windows 98/ME ?");
}



// DEMO 2: generate "Integer Divide by Zero" exception
if __try()
{
print("dividing by zero ...");
int x = 8/0;
}
// -> here <- your code continues after exception.
if __catch(&ExceptionCode, &ExceptionAddress, &pExceptionDescription)
{
ShowException(ExceptionCode, ExceptionAddress, pExceptionDescription);
}


// DEMO 3: generate "Access Violation" exception
if __try()
{
print("writing a byte to address 0x000000 ...");
int *pAddr = NULL;
*pAddr = 0;
}
if __catch(&ExceptionCode, &ExceptionAddress, &pExceptionDescription)
{
ShowException(ExceptionCode, ExceptionAddress, pExceptionDescription);
}


// DEMO 4: generate "Breakpoint" exception
if __try()
{
print("executing breakpoint int3 ...");
#emit int3
}
if __catch(&ExceptionCode, &ExceptionAddress, &pExceptionDescription)
{
ShowException(ExceptionCode, ExceptionAddress, pExceptionDescription);
}


MessageBox(0,"finito!","");
}


sub ShowException(unsigned int ExceptionCode,unsigned int ExceptionAddress,string *pExceptionDescription)
{
MessageBox(0,sprint("Exception 0x",hex$(ExceptionCode), " (",*pExceptionDescription,") at address 0x", hex$(ExceptionAddress), " occured!"),"",16);
}


the asm codeÂÃ,  gp.asm:
struc EXCEPTION_REGISTRATION
.nextÂÃ,  ÂÃ,  ÂÃ, resd 1
.handlerÂÃ,  resd 1
; user defined
.resumeÂÃ,  ÂÃ, resd 1 ;// end of __try block
.ebpÂÃ,  ÂÃ,  ÂÃ,  resd 1 ;// saved ebp
.espÂÃ,  ÂÃ,  ÂÃ,  resd 1 ;// saved esp
.codeÂÃ,  ÂÃ,  ÂÃ, resd 1 ;// ExceptionCode
.addrÂÃ,  ÂÃ,  ÂÃ, resd 1 ;// ExceptionAddress
endstruc
//-----------------------------------------------------------
align 4
__try:
popÂÃ,  eaxÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ, ; address of 'test eax, eax' - 85 C0
leaÂÃ,  ecx,[eax+8]ÂÃ,  ÂÃ, ; address after 'if __try'
addÂÃ,  eax,2ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ, ; address of jz near
addÂÃ,  eax,[eax+2]ÂÃ,  ÂÃ, ; add the offset from jz near
addÂÃ,  eax,6ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ, ; skip the jz near
xorÂÃ,  edx,edx
push edxÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  // EXCEPTION_REGISTRATION.addr
push edxÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  // EXCEPTION_REGISTRATION.code
push edxÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  // EXCEPTION_REGISTRATION.espÂÃ,  ÂÃ, // invalid!
push ebpÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  // EXCEPTION_REGISTRATION.ebp
push eaxÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  // EXCEPTION_REGISTRATION.resume
push _exception_handler // EXCEPTION_REGISTRATION.handler
push dword[fs:0]ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  // EXCEPTION_REGISTRATION.next
movÂÃ,  [fs:0],esp

movÂÃ,  [esp+EXCEPTION_REGISTRATION.esp],esp ;// fix invalid esp
jmpÂÃ,  ecx
//-----------------------------------------------------------

align 4
__leave:
popÂÃ,  ÂÃ,  ecxÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ; return address
xchgÂÃ,  ÂÃ, edx,esiÂÃ,  ÂÃ,  ÂÃ,  ; save esi in edx
movÂÃ,  ÂÃ,  esi,[fs:0]
assume esi,EXCEPTION_REGISTRATION

cmpÂÃ,  ÂÃ,  dword EXCEPTION_REGISTRATION(handler),_exception_handler
jneÂÃ,  ÂÃ, .q
popÂÃ,  ÂÃ,  dword[fs:0]
addÂÃ,  ÂÃ,  esp,sizeof(EXCEPTION_REGISTRATION)
.q:
xchgÂÃ,  ÂÃ, edx,esi
jmpÂÃ,  ÂÃ,  ecx

//-----------------------------------------------------------

align 4
__catch: ;// code*, addr*, string*
enterÂÃ,  0,0
pushÂÃ,  ÂÃ, edi
pushÂÃ,  ÂÃ, esi
movÂÃ,  ÂÃ,  esi,[fs:0]
assume esi,EXCEPTION_REGISTRATION

xorÂÃ,  ÂÃ,  eax,eax
cmpÂÃ,  ÂÃ,  dword EXCEPTION_REGISTRATION(handler),_exception_handler
jneÂÃ,  ÂÃ, .q

movÂÃ,  ÂÃ,  ecx,EXCEPTION_REGISTRATION(code)
movÂÃ,  ÂÃ,  eax,EXCEPTION_REGISTRATION(addr)
andÂÃ,  ÂÃ,  ecx,ecx
jzÂÃ,  ÂÃ,  .qÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ; here was no exception
movÂÃ,  ÂÃ,  edi,[ebp+8]
movÂÃ,  ÂÃ,  [edi],ecx
movÂÃ,  ÂÃ,  edi,[ebp+12]
movÂÃ,  ÂÃ,  [edi],eax

xorÂÃ,  ÂÃ,  edx,edxÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ; reset the exception
movÂÃ,  ÂÃ,  EXCEPTION_REGISTRATION(code),edx

; // search for exception string (code in edx, set esi to string)
movÂÃ,  ÂÃ, esi,_e_codes
.f1:
lodsd
cmpÂÃ,  ÂÃ,  eax,0
jeÂÃ,  ÂÃ,  .fq
cmpÂÃ,  ÂÃ,  eax,ecx
jeÂÃ,  ÂÃ,  .fq
.f2:
; skip string
lodsb
andÂÃ,  ÂÃ,  al,al
jnzÂÃ,  ÂÃ,  .f2
; align
movÂÃ,  ÂÃ,  eax,4
subÂÃ,  ÂÃ,  eax,esi
andÂÃ,  ÂÃ,  eax,3
addÂÃ,  ÂÃ,  esi,eax
jmpÂÃ,  ÂÃ, .f1
.fq:
movÂÃ,  ÂÃ,  edi,[ebp+16]
movÂÃ,  ÂÃ,  [edi],esi
movÂÃ,  ÂÃ,  al,1
.q:
popÂÃ,  ÂÃ,  esi
popÂÃ,  ÂÃ,  edi
leave
; retÂÃ,  ÂÃ,  12
popÂÃ,  ÂÃ,  edxÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ; return ddress
addÂÃ,  ÂÃ,  esp,12
; we are now in caller stack frame. Imitating __leave() call from the caller...
pushÂÃ,  ÂÃ, edx
jmpÂÃ,  ÂÃ,  __leaveÂÃ,  ÂÃ,  ÂÃ,  ; don't worry about eax

//-----------------------------------------------------------
align 4
_exception_handler: // EXCEPTION_RECORD*, EXCEPTION_REGISTRATION*, CONTEXT*, void *DispatcherContext)
enter 0,0
pushÂÃ,  esi
pushÂÃ,  edi
movÂÃ,  ÂÃ, edi,[ebp+12] // EXCEPTION_REGISTRATION
movÂÃ,  ÂÃ, esi,[ebp+16] // CONTEXT *

assume edi,EXCEPTION_REGISTRATION
assume esi,CONTEXT

movÂÃ,  eax,EXCEPTION_REGISTRATION(esp)
movÂÃ,  ecx,EXCEPTION_REGISTRATION(ebp)
movÂÃ,  edx,EXCEPTION_REGISTRATION(resume)

movÂÃ,  CONTEXT(esp),eax
movÂÃ,  CONTEXT(ebp),ecx
movÂÃ,  CONTEXT(eip),edx

movÂÃ,  esi,[ebp+8] // EXCEPTION_RECORD
assume esi,EXCEPTION_RECORD
movÂÃ,  eax,EXCEPTION_RECORD(ExceptionCode)
movÂÃ,  ecx,EXCEPTION_RECORD(ExceptionAddress)
movÂÃ,  EXCEPTION_REGISTRATION(code),eax
movÂÃ,  EXCEPTION_REGISTRATION(addr),ecx

popÂÃ,  edi
popÂÃ,  esi
leave
xorÂÃ,  eax,eax
ret 16


//-----------------------------------------------------------
%macro EXCEPTION 3
%push e
dd %2
db %3,0
align 4
%$
%pop
%endm

segment .data
_e_codes:
EXCEPTION DBG_CONTROL_BREAKÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ, , (0x40010008),"Control-Break"
EXCEPTION DBG_CONTROL_CÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ, , (0x40010005),"Control-C"
EXCEPTION STATUS_GUARD_PAGE_VIOLATIONÂÃ,  ÂÃ,  ÂÃ, , (0x80000001), "Guard Page Violation"
EXCEPTION STATUS_DATATYPE_MISALIGNMENTÂÃ,  ÂÃ,  , (0x80000002), "Datatype Misalignment"
EXCEPTION STATUS_BREAKPOINTÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ, , (0x80000003), "Breakpoint"
EXCEPTION STATUS_SINGLE_STEPÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  , (0x80000004), "Single Step"
EXCEPTION STATUS_ACCESS_VIOLATIONÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ, , (0xC0000005), "Access Violation"
EXCEPTION STATUS_IN_PAGE_ERRORÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  , (0xC0000006), "In Page Error"
EXCEPTION STATUS_INVALID_HANDLEÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ, , (0xC0000008), "Invalid Handle"
EXCEPTION STATUS_NO_MEMORYÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  , (0xC0000017), "No Memory"
EXCEPTION STATUS_ILLEGAL_INSTRUCTIONÂÃ,  ÂÃ,  ÂÃ,  , (0xC000001D), "Illegal Instruction"
EXCEPTION STATUS_NONCONTINUABLE_EXCEPTION , (0xC0000025), "Noncontinuable Exception"
EXCEPTION STATUS_INVALID_DISPOSITIONÂÃ,  ÂÃ,  ÂÃ,  , (0xC0000026), "Invalid Disposition"
EXCEPTION STATUS_ARRAY_BOUNDS_EXCEEDEDÂÃ,  ÂÃ,  , (0xC000008C), "Array Bounds Exceeded"
EXCEPTION STATUS_FLOAT_DENORMAL_OPERANDÂÃ,  ÂÃ, , (0xC000008D), "Float Denormal Operand"
EXCEPTION STATUS_FLOAT_DIVIDE_BY_ZEROÂÃ,  ÂÃ,  ÂÃ, , (0xC000008E), "Float Divide by Zero"
EXCEPTION STATUS_FLOAT_INEXACT_RESULTÂÃ,  ÂÃ,  ÂÃ, , (0xC000008F), "Float Inexact Result"
EXCEPTION STATUS_FLOAT_INVALID_OPERATIONÂÃ,  , (0xC0000090), "Float Invalid Operation"
EXCEPTION STATUS_FLOAT_OVERFLOWÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ, , (0xC0000091), "Float Overflow"
EXCEPTION STATUS_FLOAT_STACK_CHECKÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  , (0xC0000092), "Float Stack Check"
EXCEPTION STATUS_FLOAT_UNDERFLOWÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  , (0xC0000093), "Float Underflow"
EXCEPTION STATUS_INTEGER_DIVIDE_BY_ZEROÂÃ,  ÂÃ, , (0xC0000094), "Integer Divide by Zero"
EXCEPTION STATUS_INTEGER_OVERFLOWÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ, , (0xC0000095), "Integer Overflow"
EXCEPTION STATUS_PRIVILEGED_INSTRUCTIONÂÃ,  ÂÃ, , (0xC0000096), "Privileged Instruction"
EXCEPTION STATUS_STACK_OVERFLOWÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ, , (0xC00000FD), "Stack Overflow"
EXCEPTION STATUS_CONTROL_C_EXITÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ,  ÂÃ, , (0xC000013A), "Control-C Exit"
EXCEPTION STATUS_FLOAT_MULTIPLE_FAULTSÂÃ,  ÂÃ,  , (0xC00002B4), "Float Multiple Faults"
EXCEPTION STATUS_FLOAT_MULTIPLE_TRAPSÂÃ,  ÂÃ,  ÂÃ, , (0xC00002B5), "Float Multiple Traps"
EXCEPTION 0,0,"Unknown Exception"


A project and gp.asm with makefile attached in zip:

Steven Picard


Parker

Ditto ;)

Do you by chance have a __throw subroutine?

sapero

November 08, 2006, 12:52:03 PM #3 Last Edit: November 08, 2006, 12:59:43 PM by sapero
No problem! Uploaded new version.
Added __throw(string*) - will be ignored if no handler is active, orwill throw exception with custom code -2.
Currentry it writes a dword to address NULL to force the system to execute exception handler, here is no need to call external apis like Get/SetThreadContext.
The string message is copied to a buffer and is limited to 256 bytes, including terminating zero.
if __try()
{
void *memory = NULL;
if (!memory)
{
__throw("Failed to allocate memory!");
}
}
if __catch(&ExceptionCode, &ExceptionAddress, &pExceptionDescription)
{
MessageBox(0, "Exception raised: " + *pExceptionDescription, "", 16);
}


The __leave function has been rolled back to global function (is exported), if you do not need exception informations, call __leave() instead __catch(...)
if __try()
{
int www = 2/0;
}
__leave();


Added [fs:0] validation before read/write.
dividing by 0.0 does not throw exceptions (float flt=1/0.0), or directly (fldz \n fdivp st0).
When this will be a part of Aurora? ;D

Steven Picard

Quote from: sapero on November 08, 2006, 12:52:03 PM
When this will be a part of Aurora? ;D
I agree entirely.  I'd really like to see this as a standard part of Aurora.

Ionic Wind Support Team

It will require some testing.  It does use some interesting tricks to make it work and depends on the fact that I never change the implimentation of the IF statement.

You wouldn't want to use it in multithreaded code.
Ionic Wind Support Team

J B Wood (Zumwalt)

There is alot you don't use in multi-threaded code, unless you have an 'attach to process' since the thread is a new process, atleast as to how I understand it, I could be wrong.

It will still come in handy.

Ionic Wind Support Team

Message processing is multithreaded.  If your message handler generates another message you can get bit by global variables.
Ionic Wind Support Team

Steven Picard

How hard would it be to make a multi-threaded version of this?

Ionic Wind Support Team

Actually I just tested it and it works fine.  So no problem there.
Ionic Wind Support Team

Steven Picard


Ionic Wind Support Team

To add it to Aurora I would add a TRY macro and a CATCH macro allowing the parser to generate the needed construct provided by the IF statement.  Which frees me up to modify the implimentation of the IF statement in the future.

So TRY {...} would generate:

call __try
and eax,eax    // 2 bytes
jz near label_if_not // 2 bytes + 4 bytes offset
...
label_if_not

Also would need to come up with a keyword for the 'else' in the catch block.  Its not really the same as a FINALLY construct used in other languages.  CATCH_ELSE?

Ionic Wind Support Team

Steven Picard


sapero

CATCH_ELSE is funny :)
I have tested it in multithreaded application and it worked fine:#include "gp.inc"
import int  CloseHandle(int handle);
import int  Sleep(int time);
import void ExitThread(int code);
import int  CreateThread(void *sa,int stack,int addr,
opt void *param,opt int flags=0,opt DWORD *lpThreadId=0);


sub main()
{
int count = 0;
openconsole();

CloseHandle(CreateThread(0,0,&thread, &count));
CloseHandle(CreateThread(0,0,&thread, &count));
CloseHandle(CreateThread(0,0,&thread, &count));
CloseHandle(CreateThread(0,0,&thread, &count));
CloseHandle(CreateThread(0,0,&thread, &count));

Sleep(6<<8);
print("waiting for threads...");
while (count) {Sleep(500);}
}


sub thread(int *time)
{
*time++;
int number = *time;
print("thread ", number, " running");
Sleep(number<<8);

if __try()
{
int x = 0/0;
}
if __catch()
{
MessageBox(0, sprint("exception in thread ", number), "", 0x80000000);
}
*time--;
print("thread ", number, " exiting");
ExitThread(0);
}


but here was a problem with this code: (the division was performed before call to try)
if __try() int x=0/0;

Parker

Can ELSE be reused with a catch block?

try
{
...
}
catch (Exception e)
{

}
else
{

}


-- If the ELSE comes right after a catch block it is a "catch else", otherwise it is an "if else". Also I would assume __leave is inserted automatically if no catch block is found, and class based exception handling like in Java and other languages would be nice if possible, instead of the string based approach. Is it possible to just change the string parameter in __throw and __catch to a CException class pointer (that would be built in) without breaking anything? That would be an excellent opportunity for parameters in constructors:
if( something_is_wrong ) throw new MyException( "Something went wrong", something_value );

... Sorry, I got carried away. All things to think about though...

Ionic Wind Support Team

You want fries with that order?  How about a nice frosty milkshake.
Ionic Wind Support Team

Parker


Mike Stefanik

This will be a great (and much needed) addition to the language.
Mike Stefanik
www.catalyst.com
Catalyst Development Corporation

Zen

Wow this is awesome!

I feel so ashamed lol. I make silly little programs that do hardly anything, but when they work im all like wow cool! Then there's Sapero who comes along and is like look what else i've hacked into Aurora today ;)

Just joking of course. Sapero you are a good influence to everyone and someone to look upto aswell as  Paul. Well at least for me anyway ;)

Lewis

sapero

Paul, be sure to remove the g_user_message string from static variables, it is here only for demo purpose, for one thread app. This string should be saved in in EXCEPTION_REGISTRATION as string or better, strdup()
And small note about __leave:
1. the custom EXCEPTION_REGISTRATION structure has unused member .userdata - you can remove it.
2. stack cleanup goes in two steps:
pop dword[fs:0]
add esp,sizeof(EXCEPTION_REGISTRATION) -4

the -4 was missing, sorry

J B Wood (Zumwalt)

I think CATCH_ELSE is cool, and fully defines what the else is for.
You have no idea how badly I needed this.

Zen

Quote from: Jonathan (zumwalt) Wood on November 09, 2006, 07:39:11 AM
You have no idea how badly I needed this.

Like a kid too close to christams to wait? :P

Lewis

J B Wood (Zumwalt)


Zen

Hey paul. Just wondering how the implementation of this is coming along and do you have am eta? I am just anctious to play as usual

Lewis

Ionic Wind Support Team

See my post in the developers forum. 
Ionic Wind Support Team