Home
symbianos' Journal
 
[Most Recent Entries] [Calendar View] [Friends]

Below are the 8 most recent journal entries recorded in symbianos' LiveJournal:

    Monday, December 15th, 2003
    10:11 pm
    debugging - some tips
    When you get an ALLOC: hhhhhhhh panic, it simply means you have a memory leak in your application. This article is a good starting point to help you discover where the memory leak is in your application :
    http://www2.symbiandevnet.com/kbcppf.nsf/0/aa8ae5b603693554412568c600344733?OpenDocument
    You can use the same technique to debug Leaves. Go to Edit/Breakpoints, and type break at: User::Leave(int). When you hit the breakpoint the value of the EAX register will be the Leave reason (error code). You will catch each Leave in the OS, and there are many of those.
    So you might want to make the breakpoint conditional. Add the condition: EAX==your_leave_reason. This will only break leaves with that particular leave reason. Those could be many as well. Anyway, most importantly, you'll get a stack trace and you'll be able to locate the problem (hopefully).
    Same thing for USER-EXEC 3 exceptions. Just put a breakpoint on RThread::RaiseException(TExcType). The EAX register will contain the exception type. See TExcType enumeration for possible values. If the thread doesn't have an exception handler, the exception will turn into a USER-EXEC 3 panic. See also a previous post about how to turn USER-EXEC 3 panics into Leaves which you can trap with TRAP/TRAPD. This breakpoint will again give you a stack you can work with in order to debug the problem.
    However, the above technique will only work if you can reproduce the problem in the emulator.
    For target problems, see my previous post of installing an exception handler on the thread that gets the USER-EXEC 3 exception. In order to locate the offending code, you can use the old mechanism of debug traces or checkpoints.
    If you are a licensee or a partner and have access to Symbian code, you can add some code at the beginning of User::Leave (to catch leaves on target) or RThread::RaiseException (to catch USER-EXEC 3 panics on target). The code is something like:

    TInt caller_address;
    asm volatile ("str lr, %0" : "=m" (caller_address) );
    RDebug::Print(_L("RThread::RaiseException on Thread=%S Caller_Address=0X%x"), RThread().FullName(), caller_address);

    lr is the ARM register that is used as the return address from a function, so it's an address in the function that called the current function, so it will provide you with an address in the function that called User::Leave or RThread::RaiseException. By looking at the symbols for the ROM you built, you can tell where that address lies, and then you can investigate further.
    Friday, December 12th, 2003
    10:10 pm
    stack, heaps
    The default stack size in EPOC is 8KB. Unlike other OSes like Windows, the stack does not grow, so when it's over ... well, it's over. And when it's over, it means you're trying to access an address that's not mapped into your process' address space, which will generate a MMU exception which in turn the kernel will transform into a KERN-EXEC 3 panic into your application. Stack overflow is treated as access violation. Sometimes that proves hard to debug.
    Now here's the trick ... on the emulator this will not happen because Windows as I said before grows the stack if you need more. This simply means that you could end up with an application that works on the emulator but it gets panicked on the target platform.
    If you want to specify a different size for stack, you can. In your mmp file include a statement: epocstracksize your_stack_size_in_bytes. It can be either in decimal or hexadecimal format.
    Generally if you need large objects, a large TBuf for instance, it's better to put them on the heap (ie use HBufC...). That's because unlike stacks, heaps can grow if necessary up to a maximum size specified at the creation of the heap. Every heap is specified in terms of minimum heap size (default value=1KB) and maximum heap size (default value=1Mb). You can specify different values in the mmp file by using the statements: epocheapsize minimum maximum. Either values can be specified in decimal or hexadecimal format. The value you specify
    What's interesting in Symbian OS about heaps is that they are actually associated with threads rather than processes. If that's usually true for stacks in other OSes, it's not for heaps. Usually a heap is a process resource, and access to the heap is synchronised. However in Symbian, heap is a thread resource, so each thread has its stack but also its heap. That will increase allocation time, as there's no longer necessary to synchronise access to it (when allocating and deallocating).
    There's is wide degree of flexibility though. When you create a thread programatically, you can specify if you want a new heap to be constructed by specifying its heap min size and heap max size, you can also specify that the thread should use a specified heap (RHeap*) or if you pass NULL as the value for RHeap, you can share a heap with the thread that's creating the new thread. So you can have all your threads sharing the same heap if you want.
    Another interesting feature is that the stack and the heap (in case the thread has its own heap) are located in the same chunk. At the base of the chunk we have the stack, which however starts from the address equal with the base of the chunk + the size of the stack and runs to low addresses so you run out of stack when you try to access an address lower than the base of the chunk. The heap starts from the same place where stack starts + 1 word and it runs to higher addresses.
    The heap has a property of growing when it needs space, and it grows with an amount specified by iGrowBy, which is passed to the constructor of RHeap. The GrowBy will be rounded up to page size, which on ARM is 4KB. The allocated size for the heap will always be a multiple of 4KB.
    One interesting aspect that happens in debug builds only (so you won't see it in a phone but only if you work on a reference platform, you've paid lots of money to Symbian and you have a debug build :) - when the heap is initialised, it's filled with 0xA5. When a heap cell is freed, it's filled with 0xDE. So when you see a pointer like 0xDEDEDEDE, it is a good guess that it's a dangling pointer, something you've deleted but you might still be using. It's useful when debugging.
    Friday, November 28th, 2003
    10:06 pm
    Why do I get a USER-EXEC 3 in my application?
    USER-EXEC is a panic category that happens when an exception is raised on a thread and it's not handled (ie there's no registered handler for that exception). You can install an exception handler on a thread by calling RThread::SetExceptionHandler. The first parameter is a pointer to a handler - a function of type TExceptionHandler:
    typedef void TExceptionHandler(TExcType);
    The second parameter is a mask where you specify what kind of exceptions you want to catch.
    Possible values are below:
    const TUint KExceptionAbort=0x01;
    const TUint KExceptionKill=0x02;
    const TUint KExceptionUserInterrupt=0x04;
    const TUint KExceptionFpe=0x08;
    const TUint KExceptionFault=0x10;
    const TUint KExceptionInteger=0x20;
    const TUint KExceptionDebug=0x40;
    The common reasons for such a panic are floating point operations, divisions by zero, alignment problems. The way to find out exactly the type of exception is to install an exception handler on your thread and your handler will get called with a parameter of type TExcType which will specify the type of exception. However you will not be able to identify where it happened or look at the contents of the exception at the moment when the exception was thrown. All you can do is Leave from the handler which will transform the exception into a Leave. That way you could TRAP such an exception. But you'll have to know where it happens first.
    Some code below:
    ...
    RThread().SetExceptionHandler(&ExceptionHandler, KExceptionInteger);
    TInt64 i;
    i=0;
    test.Printf(_L("checkpoint 1"));
    TInt64 k=34000;
    TRAPD(lcode, TInt64 j = k/i); //this will cause a divide by 0 exception
    test.Printf(_L("checkpoint 2 - leave code %d"), lcode); //lcode will be KErrDivideByZero
    ...
    where ExceptionHandler is define as:
    void ExceptionHandler(TExcType)
    {
    RDebug::Print(_L("Exception handler"));
    User::Leave(KErrDivideByZero);
    }
    Is it useful? Maybe not, as exception usually indicate programming errors, so there will be little value in catching them at run time. Moreover, using TRAP on every operation that could generate such an exception will bloat the code dramatically, so it's not recommended for production code. It might be helpful during debugging.
    64-bit ints and floating point operations are not natively supported by the ARM processor, so these exceptions are generated in software, not by the hardware. So these exceptions are converted to exceptions by the OS from error code (eg KErrDivideByZero becomes an EExcIntegerDivideByZero exception). This is an however an implementation, it could change in the future, if the ARM processor introduces new type of exceptions for instance.
    USER-EXEC 3 is a panic though, it's generated by an unhandled exception on the current thread. You can't catch a panic.
    And finally, what's the difference between USER-EXEC 3 and KERN-EXEC 3. 3 here means the same thing: some operation caused an exception which was unhandled and became a panic. The different category suggests if the exception on the thread happened while the thread was running in User or Kernel mode. So KERN-EXEC usually happen while the thread is running in an executive call (system call). USER-EXEC means the exception was caused while the thread was running in user mode.
    Tuesday, November 25th, 2003
    10:04 pm
    Leaves, panics, exceptions, faults
    Do you know the exact difference between all these?
    Well, here it is if you don't...

    A leave is an exception. From an architectural point of view you can think of them as exceptions. They should mark exceptional code, that is, something a user doesn't expect and it's not related to the computation. Situation like out of memory, out of disk space, failure to acquire a certain resource, etc. these are situations that a user will not expect and probably their code won't cope with such errors. Returning an error code in such circumstances is BAD BAD BAD. It's evil. Instead you should Leave, and the appropriate level of the framework will deal with it. Probably shutting down some applications in case of out of memory or deleting some temporary files etc. Leaves(exceptions) are there for marking such exceptional situations. Leaves and TRAPs do cost. They represent a runtime overhead, so use with care. They're implemented in a way similar to setjmp/longjmp with the addition that they delete the objects pushed on the cleanup stack. They do not delete the other objects on the stack or heap that are not pushed on the cleanup stack (so always push your resources on the cleanup stack if you want their destructors being called when a Leave occurs). The "leave" code gives you an idea of what went wrong.

    Panics represent programming errors. They are not made to be handled at runtime but are helpers during development time. When your thread/application is panicked, it's like saying that it has a bug and you should fix it before releasing it. Do not expect and do not try to catch panics, you can't catch panics by using TRAP (TRAPs are only for "leaves"). Have a look at the panic code and panic category and check the documentation to see exactly what that panic code and panic category means. This will hopefully tell you what you're doing wrong. Off the top of my head, there are 2 very popular panics. "KERN-EXEC 3" and "KERN-EXEC 0". Here "KERN-EXEC" is the category which basically tells you that the kernel killed your thread. Exit code 3 means Access violation. You tried to write to an address which is not mapped into your address space. This usually means NULL pointer dereferentiation or deleting the same pointer twice when the value of the pointer is no longer valid. KERN-EXEC 0 is generated by an invalid handle. Most "R" classes represent resources and most resources have a handle to the resource that usually resides kernel side or in a server. If you have an invalid handle you'll be killed with this category/code. An easy way to reproduce it is by opening a session with a server, use the session again. After closing the handle, it becomes invalid.

    Exceptions are generated by the hardware. Please consult an ARM reference manual to see the types of exceptions raised by a certain type of ARM processor. For instance accessing an address that's not mapped into your address space generates such an exception raised by the MMU. Using an invalid pointer (a special case would be a NULL pointer) is just one way of generating such an exception. Another, maybe more subtle exception is stack overflow. A famous exception for ARM hardware is the alignement exception. If you try to load a word from an address that's not word-aligned for instance, an exception will be raised. If unhandled, exceptions become panics for most user threads (actually they become a "KERN-EXEC 3" to be precise) or they become faults if they happen kernel side, or in critical user threads like the file server. You can handle the exceptions on your thread by installing a handling routine. Use RThread::SetExceptionHandler. You can test it by raising exceptions on your thread with RThread::RaiseException.

    Faults only happen kernel side or if a critical user thread panics or gets an exception. Faults are critical errors and it means that the OS cannot continue to work properly. In a development environment this will end up in a debug monitor. On a real world device, a fault will generally result in your phone/pda being rebooted. A critical user thread is one that is essential to the OS. File Server and Window server are two such servers that have critical user threads. If any of the critical user thread terminates, the OS cannot continue to operate correctly. That's the reason why a fault is raised at such point. Rebooting the phone/pda in a real world environment is just a conveniance of letting the user still use a phone if some obscure feature manages (by absurd :) to fault the system. In other words you shouldn't experience faults unless you write your own EPOC device drivers. In theory :-)

    Now to sum it up. TRAP will only catch Leaves, will not catch panics, exceptions (there's is a mechanism to enable such thing but only for kernel side code) or faults. So don't even try to do that, it will be a waste of ROM/RAM and of precious run-time cycles. There is a way to trap exceptions by installing your exception handler on your thread. There's no way to trap panics or faults since these represent programming errors.
    An untrapped Leave (by TRAP or TRAPD) might be trapped by the lower levels of the framework that you're using. Check each case and see what happens and what the result is. If untrapped, the thread will be panicked and it will stop execution. A panic will stop the execution of your thread (period). Cannot be trapped. An exception, if not handled, will become a panic if the thread that gets it is a normal user thread or into a fault if it happens kernel side or in a critical user thread. A fault is a serious problem in the OS and as an application developer you shouldn't experience it. It's like a un-recoverable panic. The whole OS goes down.
    Friday, November 21st, 2003
    10:03 pm
    epocwind.out
    On a target device, the debug output is sent on the selected COM port. In emulator it's sent to a file called epocwind.out. You can use RDebug.Print to output debug text. RTest framework also uses the same debug output facility to write the name of the test, anything that it prints using RTest::Printf or the result either a failure or a pass.
    So when you run a test, this is the file you want to investigate for errors or for successful completion.
    Where is it located?
    The emulator uses the TEMP environment variable to output the file, so it's in %TEMP%\epocwind.out. TEMP variable in NT is C:\TEMP usually and on Win2K and onwards, each user has a temp directory in Local Settings. But you can change the value of your TEMP variable if you want epocwind.out (and other Windows temporary files) to end up in a different place.
    Thursday, November 20th, 2003
    10:02 pm
    new(ELeave)?
    What's that? I'm sure many programmers have been puzzled by this construct when they first saw it. So what's this strange construct that's so much used in SymbianOS?
    Well, some background ... C++ specifies 2 versions of new operator, the one that throws an exception and the one that return NULL. If you come from a C background the one returning NULL is more familiar and you tend to like that one and ignore the other. It's because it acts pretty much as malloc does. However throwing an exception is slightly different. I won't get into details but indeed it's always better to stop an application when a fault occurs rather than let it continue and wait for it to crash randomly.
    At the moment Symbian OS was firstly developed there was no support for C++ exceptions in compilers. So a new technique had to be developed to do the same thing. And that's how the concept of "leaving" came into being. Leaving from a function it's like throwing and exception.
    If you want the operator new to Leave instead of returning NULL, you have to use new(ELeave) instead of simple new. The construct takes advantage of one of C++ feature called placement new. It actually allows you to add parameters to the new operator that can specify additional operations (ie specify that the new allocated cell should be placed at a certain address on the heap(or free store)). It was used primarily for placement, when you want to create an instance of your class at a specific location. However you can whatever you like with the extra parameter. So, in Symbian OS a new parameter of type TLeave is specified when you call new(ELeave). The TLeave parameter is ignored by the operator new, it's just to tell the compiler to use another function call (prototype differentiation).
    TLeave looks like:
    enum TLeave {ELeave}
    so ELeave is 0, but as it's ignored it could be anything really. It could be:
    new((TLeave)1234) for all Symbian OS cares. It's just to tell the compiler to call
    TAny* operator new(TUint aSize, TLeave);
    instead of
    TAny* operator new(TUint aSize);
    The first API is a call to User::AllocL while the second is a call to User::Alloc.
    When possible, use new(ELeave) instead of the standard new operator.
    Wednesday, November 19th, 2003
    10:01 pm
    RArray
    There are at least 2 sets of array API related to arrays in Symbian OS; the CArray... family and RArray... family. If you're particularly interested in performance you should use RArray as it's optimised and it's a newer API.
    However there's one interesting thing about RArray that not many people know about as it's not documented properly. RArray rounds up the entry size so that it becomes a multiple of 4. This is done for optimisation. It also expects pointers to be word aligned.
    For instance, if you use the following constructor:
    inline RArray::RArray(TInt aEntrySize,T* aEntries, TInt aCount)
    whereby T is a template class, aEntries must be word aligned (ie its address a multiple of 4). aEntrySize will be round up to a multiple of 4.
    Thus, the internal storage will be a multiple of 4 in order to be able to do word loads on ARM. This will waste space if you're using RArray with objects with sizes that are not multiple of 4. RArray or RArray for instance. The internal storage uses 4 bytes for each half-word.
    Also if you use the following API to append entries:
    inline TInt RArray::Append(const T& anEntry)
    anEntry MUST be word aligned here. This will usually happen as the gcc will do that for you for certain types. However it might not be aligned for TInt16 or TUint16 or bytes...
    So remember, RArray APIs stores values on word aligned addresses and uses multiple of 4 bytes to store values. If you pass an unaligned address to the constructor or to Append, you'll get an ARM exception which will result in your thread being panicked or the kernel being faulted if you run kernel side.
    Tuesday, November 18th, 2003
    9:58 pm
    I've decided to start up with blog with the idea that it might help some developers get a closer understanding of Symbian OS. I hope the information I'm going to opst in here will not harm anyone as it's for educational purposes only. However if you spot something that's not suppose to be here, let me know and I'll remove it.
    Symbian OS is a relatively young OS, the developer community is still in its infancy and many times I've heard people complaining about documentation being insufficient, about programming patterns being different and so on...
    It is an OS written in C++, which was a misfortunate choice as in '95 when it was started C++ was an immature programming language (it still tends to be today, although it's quite elegant). This choice generated serious binary compatibility problems later on and also strange constructions for handling exceptions. They're called "Leaves". Leave corresponds to a throw statement, and a TRAP, TRAPD corresponds to a try statement. TRAP returns the exception code (error code) which the function that leaves returns. This was require as back in '95 gcc and other C++ compilers didn't supported try/catch exception handling.
    It is a phone OS and care was taken in ensuring RAM/ROM footprint and resource usage is kept to minimum.
About LiveJournal.com

Advertisement