Debugging techniques
From Forum Nokia Wiki
Contents[hide] |
The emulator doesn’t display the details of a panic
If a panic occurs, the emulator doesn’t display its details unless there is a file named “ErrRd” in a specific location. This makes it difficult to know what caused the panic.
Before the 3rd edition of the SDK, the ErrRd file must be created manually, but from the 3rd edition onwards it can be found by default from directory “C:/Symbian/9.2/S60_3rd_FP1/Epoc32/winscw/c/resource”. With ErrRd, the output looks like this, in case a panic occurs:
Tip: If the ErrRd file cannot be found even if you are using 3rd edition SDK, start the emulator, select Tools > Preferences and check Extended panic code file.
Bug detection using assertions
Assertions are used to check that assumptions made about code are correct and that the state, for example, of objects, function parameters or return values, is as expected. There are two assertion macros defined on Symbian OS, __ASSERT_ALWAYS and __ASSERT_DEBUG. The difference between them is that __ASSERT_DEBUG will not affect production code whereas __ASSERT_ALWAYS will. Here’s an example of how to use the __ASSERT_DEBUG macro:
void TestValue(TInt aValue)
{
_LIT(KPanicCategory, "TestValue");
__ASSERT_DEBUG((aValue >= 0), User::Panic(KPanicCategory, 99));
// Do something with aValue
// ...
}
In the example above, a panic -99 is raised if the parameter aValue is less than 0.
Note: The assertion macros do not panic by default, allowing you to decide what procedure to call in case assertion fails. Despite this, you should always raise a panic in this case rather than return an error or leave.
Because the example above uses the __ASSERT_DEBUG macro, aValue is only tested in debug builds. If it is necessary to test the parameter also in production code, __ASSERT_ALWAYS should be used.
When you don’t expect external callers to need to trace a panic, an alternative to using __ASSERT_DEBUG is ASSERT macro. ASSERT is exactly like debug assertion macro except that it doesn’t need you to provide a panic category or descriptor. Here’s the definition of the macro from e32def.h file:
#define ASSERT(x) __ASSERT_DEBUG(x, User::Invariant())
Here’s an example of how to use the ASSERT macro:
void TestValue(TInt aValue)
{
ASSERT(aValue >= 0);
// Do something with aValue
// ...
}
Detecting memory leaks with __UHEAP_MARK and __UHEAP_MARKEND macros
One possibility to check that your code is managing heap memory correctly, in other words not leaking memory, is to use __UHEAP_MARK and __UHEAP_MARKEND macros. Here is an example:
GLDEF_C TInt E32Main()
{
// Start checking memory leaks
__UHEAP_MARK;
// Create a fixed-length, flat array, which contains 10 integers
CArrayFixFlat<TInt>* fixFlatArray;
fixFlatArray = new(ELeave) CArrayFixFlat<TInt>(10);
// Array is not deleted, so memory will leak
// Stop checking memory leaks and cause a panic if there is a leak
__UHEAP_MARKEND;
return KErrNone;
}
Due to the array not being deleted and because of memory leak detecting macros, the code example above will cause a panic when the application is closed, as shown in figure below:
It is worth mentioning that heap-checking macros are only compiled into debug builds, so they can be safely left in the production code without having any impact on the code size or speed.
Object invariance macros
There are two macros that allow you to check the state of an object, __DECLARE_TEST and __TEST_INVARIANT. In practice they are used so that the programmer first creates an invariance test function, which is then called, typically at the beginning and end of a function in which object’s state needs to be checked. Here’s an example of a class that represents a living person and uses invariance testing to verify that the person has a gender and his or her age is not negative:
class CLivingPerson : public CBase
{
public:
enum TGender {EMale, EFemale};
public:
CLivingPerson(TGender aGender);
~CLivingPerson();
public:
void SetAge(const TInt aAge);
private:
TGender iGender;
TInt iAgeInYears;
__DECLARE_TEST; // Object invariance testing
};
CLivingPerson::CLivingPerson(TGender aGender) : iGender(aGender) {}
CLivingPerson::~CLivingPerson() {}
void CLivingPerson::SetAge(const TInt aAge)
{
// Set age and check object invariance
__TEST_INVARIANT;
iAgeInYears = aAge;
__TEST_INVARIANT;
}
void CLivingPerson::__DbgTestInvariant() const
{
#ifdef _DEBUG // Built into debug code only
// Person should be either male or female
ASSERT((iGender == EMale) || (iGender == EFemale));
// Person's age shouldn't be negative
ASSERT(iAgeInYears >= 0);
#endif
}
Due to the use of ASSERT macros in the example above, a USER 0 panic is raised if the object’s state is incorrect.
Detecting incorrect use of cleanup stack with expected items
Objects on the cleanup stack should be popped when there is no longer a chance that they would be orphaned by a leave. Thus, popping usually happens just before the objects are deleted. PopAndDestroy function is normally used instead of Pop, as this ensures the object is deleted as soon as it is popped, which avoids the possibility for a memory leak. Both CleanupStack::Pop and CleanupStack::PopAndDestroy have an overloaded form that allows the caller to declare an “expected item”, an item that should be popped from the stack. In case expected item doesn’t match the popped item, an E32USER-CBase 90 panic is raised. These two overloaded forms are the recommended ones, because they help detect incorrect use of the cleanup stack.
CClass* obj = new(ELeave) CClass;
CleanupStack::PushL(obj);
// ...
CleanupStack::PopAndDestroy(obj); // Panics if ‘obj’ not on top
<script src="/ad/contentg.js">
</script>
模拟器不显示Panic细节
若Panic发生了,除非在指定的位置有一个名叫“ErrRd”的文件,否则模拟器不会显示Panic的细节。这使得很难知道是什么引起Panic。
在SDK 3rd版以前,ErrRd文件必须手工创建,但从3rd版以后,这个文件可以默认在目录“C:/Symbian/9.2/S60_3rd_FP1/Epoc32/winscw/c/resource”下找到。有了ErrRd,Panic发生时的输出像这样:
提示: 如果即使使用3rd版的SDK,ErrRd文件也找不到,那就启动模拟器,选择 Tools > Preferences,然后勾上Extended panic code file。
使用断言检测Bug
使用断言检测所做的“代码是正确的”的假设,例如: 对象的状态,期望的函数参数和返回值等。Symbian OS中定义了两个断言宏: __ASSERT_ALWAYS 和__ASSERT_DEBUG。它们之间的区别是__ASSERT_DEBUG不会影响产品代码而__ASSERT_ALWAYS会。
这是一个如何使用__ASSERT_DEBUG宏的例子:
void TestValue(TInt aValue)
{
_LIT(KPanicCategory, "TestValue");
__ASSERT_DEBUG((aValue >= 0), User::Panic(KPanicCategory, 99));
// Do something with aValue
// ...
}
上例中,如果参数aValue小于0,抛出"Panic -99"。
注: 断言宏默认不抛Panic, 允许你来决定断言失败时调用什么过程。尽管如此,这种情况下你应该总是抛出Panic而不是返回错误或Leave。
因为上例使用__ASSERT_DEBUG宏,只在debug编译时才检测aValue。如果有必要在产品代码中也检测参数,就应当用__ASSERT_ALWAYS。
当你不希望外部调用者需要跟踪Panic时,使用__ASSERT_DEBUG的一个替代品: ASSERT宏。ASSERT完全象是断言宏,除了它不要要你提供panic类别或描述符。
这里是该宏的定义,来自e32def.h文件:
#define ASSERT(x) __ASSERT_DEBUG(x, User::Invariant())
这是一个如何使用ASSERT宏的例子:
void TestValue(TInt aValue)
{
ASSERT(aValue >= 0);
// Do something with aValue
// ...
}
使用__UHEAP_MARK和__UHEAP_MARKEND宏检测内存泄漏
检测你的代码正确地管理堆内存(换言之,不泄漏内存)的一个可能性是使用__UHEAP_MARK和__UHEAP_MARKEND宏。
GLDEF_C TInt E32Main()
{
// Start checking memory leaks
__UHEAP_MARK;
// Create a fixed-length, flat array, which contains 10 integers
CArrayFixFlat<TInt>* fixFlatArray;
fixFlatArray = new(ELeave) CArrayFixFlat<TInt>(10);
// Array is not deleted, so memory will leak
// Stop checking memory leaks and cause a panic if there is a leak
__UHEAP_MARKEND;
return KErrNone;
}
由于数据未被删除和内存泄漏检测宏,上例代码在应用程序关闭时将引起一个Panic,如下图所示:
值得一提的是堆检测宏只编译进debug版,因此可以安全地留在产品代码中而不会影响代码的大小或速度。
对象不变性宏
有两个宏允许你检查对象的状态: __DECLARE_TEST 和 __TEST_INVARIANT。在实践中,它们被用来使程序员先创建一个不变性测试函数,然后在需要检测对象状态的函数的开头和结尾调用之,这是典型的做法。
class CLivingPerson : public CBase
{
public:
enum TGender {EMale, EFemale};
public:
CLivingPerson(TGender aGender);
~CLivingPerson();
public:
void SetAge(const TInt aAge);
private:
TGender iGender;
TInt iAgeInYears;
__DECLARE_TEST; // Object invariance testing
};
CLivingPerson::CLivingPerson(TGender aGender) : iGender(aGender) {}
CLivingPerson::~CLivingPerson() {}
void CLivingPerson::SetAge(const TInt aAge)
{
// Set age and check object invariance
__TEST_INVARIANT;
iAgeInYears = aAge;
__TEST_INVARIANT;
}
void CLivingPerson::__DbgTestInvariant() const
{
#ifdef _DEBUG // Built into debug code only
// Person should be either male or female
ASSERT((iGender == EMale) || (iGender == EFemale));
// Person's age shouldn't be negative
ASSERT(iAgeInYears >= 0);
#endif
}
由于上例使用ASSERT宏,若对象状态不正确时就抛出"USER 0"Panic。
用期望的弹出项检测清除栈的不正确使用
清除栈中的对象在不再有机会被Leave成孤儿时应被弹出。因此,弹出通常恰好发生在对象被删除之前。一般使用PopAndDestroy函数代替Pop函数,因为它确保对象一弹出就被删除,避免了内存泄漏的可能性。CleanupStack::Pop 和 CleanupStack::PopAndDestroy都有一个重载版本,允许调用者声明“期望的弹出项”,指明该项应从栈中弹出。在期望的弹出项不匹配所弹出的项时,将抛出"E32USER-CBase 90" Painc。推荐使用这两个重载版本,因为它们帮助检测清除栈的不正确使用。
CClass* obj = new(ELeave) CClass;
CleanupStack::PushL(obj);
// ...
CleanupStack::PopAndDestroy(obj); // Panics if ‘obj’ not on top
注: 在发行版编译时,期望弹出项的检测将被禁用,因此使用它们在二进制大小和效率方面都不会影响发行版编译。