前言
好的,那前面的例子我们分析完了,我们发现一个问题,就是在函数体内进行sp的初始化,也会对对象的整个引用计数进行操作,那么在函数的调用关系中,其实很容易就迷失在各种加加减减的引用指针计数中。那么我们应该怎么样去进行debug呢。还好,我们遇到的问题,写这个RefBase公共类的人也同样遇到了。所以他们定义了对应的debug信息。那么我们来看一下怎么样来进行调试吧。
DEBUG开关:
// Compile with refcounting debugging enabled.
#define DEBUG_REFS 0
// The following three are ignored unless DEBUG_REFS is set.
// whether ref-tracking is enabled by default, if not, trackMe(true, false)
// needs to be called explicitly
#define DEBUG_REFS_ENABLED_BY_DEFAULT 0
// whether callstack are collected (significantly slows things down)
#define DEBUG_REFS_CALLSTACK_ENABLED 1
// folder where stack traces are saved when DEBUG_REFS is enabled
// this folder needs to exist and be writable
#define DEBUG_REFS_CALLSTACK_PATH "/data/debug"
// log all reference counting operations
#define PRINT_REFS 0
// Continue after logging a stack trace if ~RefBase discovers that reference
// count has never been incremented. Normally we conspicuously crash in that
// case.
#define DEBUG_REFBASE_DESTRUCTION 1
备注里也写的很关键,就是第一个DEBUG_REFS打开后,后面的这些debug开关才会有作用。那这个是为什么我们后面再看,先说一下这几个的含义。
1.DEBUG_REFS_ENABLED_BY_DEFAULT 如果打开就会每次都track每一个对象
2.DEBUG_REFS_CALLSTACK_ENABLED 这个从名字就可以看出,是用来跟踪调用栈的
3.PRINT_REFS 打印log
为什么需要打开DEBUG_REFS
还记得我们之前说的那个空实现吗?其实就是用这个变量来进行定义的。
class RefBase::weakref_impl : public RefBase::weakref_type
{
public:
std::atomic<int32_t> mStrong;
std::atomic<int32_t> mWeak;
RefBase* const mBase;
std::atomic<int32_t> mFlags;
#if !DEBUG_REFS
explicit weakref_impl(RefBase* base)
: mStrong(INITIAL_STRONG_VALUE)
, mWeak(0)
, mBase(base)
, mFlags(0)
{
}
void addStrongRef(const void* /*id*/) { }
void removeStrongRef(const void* /*id*/) { }
void renameStrongRefId(const void* /*old_id*/, const void* /*new_id*/) { }
void addWeakRef(const void* /*id*/) { }
void removeWeakRef(const void* /*id*/) { }
void renameWeakRefId(const void* /*old_id*/, const void* /*new_id*/) { }
void printRefs() const { }
void trackMe(bool, bool) { }
#else
weakref_impl(RefBase* base)
: mStrong(INITIAL_STRONG_VALUE)
, mWeak(0)
, mBase(base)
, mFlags(0)
, mStrongRefs(NULL)
, mWeakRefs(NULL)
, mTrackEnabled(!!DEBUG_REFS_ENABLED_BY_DEFAULT)
, mRetain(false)
{
}
~weakref_impl()
{
bool dumpStack = false;
if (!mRetain && mStrongRefs != NULL) {
dumpStack = true;
ALOGE("Strong references remain:");
ref_entry* refs = mStrongRefs;
while (refs) {
char inc = refs->ref >= 0 ? '+' : '-';
ALOGD("\t%c ID %p (ref %d):", inc, refs->id, refs->ref);
#if DEBUG_REFS_CALLSTACK_ENABLED
CallStack::logStack(LOG_TAG, refs->stack.get());
#endif
refs = refs->next;
}
}
if (!mRetain && mWeakRefs != NULL) {
dumpStack = true;
ALOGE("Weak references remain!");
ref_entry* refs = mWeakRefs;
while (refs) {
char inc = refs->ref >= 0 ? '+' : '-';
ALOGD("\t%c ID %p (ref %d):", inc, refs->id, refs->ref);
#if DEBUG_REFS_CALLSTACK_ENABLED
CallStack::logStack(LOG_TAG, refs->stack.get());
#endif
refs = refs->next;
}
}
if (dumpStack) {
ALOGE("above errors at:");
CallStack::logStack(LOG_TAG);
}
}
void addStrongRef(const void* id) {
//ALOGD_IF(mTrackEnabled,
// "addStrongRef: RefBase=%p, id=%p", mBase, id);
addRef(&mStrongRefs, id, mStrong.load(std::memory_order_relaxed));
}
看这个地方,因为debug定义的不同而导致参与编译的函数不同,而在非debug版本中,所有的实现都是空函数。而在debug版本中,会将每一次的增加和减少操作都进行记录。比如我们在incStrong的时候。
void RefBase::incStrong(const void* id) const
{
weakref_impl* const refs = mRefs;
refs->incWeak(id);
refs->addStrongRef(id); // 这个地方的空调用。
const int32_t c = refs->mStrong.fetch_add(1, std::memory_order_relaxed);
ALOG_ASSERT(c > 0, "incStrong() called on %p after last strong ref", refs);
#if PRINT_REFS
ALOGD("incStrong of %p from %p: cnt=%d\n", this, id, c);
#endif
if (c != INITIAL_STRONG_VALUE) {
return;
}
int32_t old __unused = refs->mStrong.fetch_sub(INITIAL_STRONG_VALUE, std::memory_order_relaxed);
// A decStrong() must still happen after us.
ALOG_ASSERT(old > INITIAL_STRONG_VALUE, "0x%x too small", old);
refs->mBase->onFirstRef();
}
而在debug的版本上,这个地方就不是空的调用了。
void addStrongRef(const void* id) {
//ALOGD_IF(mTrackEnabled,
// "addStrongRef: RefBase=%p, id=%p", mBase, id);
addRef(&mStrongRefs, id, mStrong.load(std::memory_order_relaxed));
}
void addRef(ref_entry** refs, const void* id, int32_t mRef)
{
if (mTrackEnabled) {
AutoMutex _l(mMutex);
ref_entry* ref = new ref_entry;
// Reference count at the time of the snapshot, but before the
// update. Positive value means we increment, negative--we
// decrement the reference count.
ref->ref = mRef;
ref->id = id;
#if DEBUG_REFS_CALLSTACK_ENABLED
ref->stack = CallStack::getCurrent(2);
#endif
ref->next = *refs;
*refs = ref;
}
}
可以看到,就是在进行加减的时候对对应的对象进行了记录操作,而记录就放在对应的ref_entry中。那么,我们只需要将对应的记录打印出来即可。
trackme
void trackMe(bool track, bool retain)
{
mTrackEnabled = track;
mRetain = retain;
}
在trackme中配置对应的跟踪标记位置,然后就可以在对应的标记位置打印信息或者是抓取到堆栈。查看一个示例
void renameRefsId(ref_entry* r, const void* old_id, const void* new_id)
{
if (mTrackEnabled) {
AutoMutex _l(mMutex);
ref_entry* ref = r;
while (ref != NULL) {
if (ref->id == old_id) {
ref->id = new_id;
}
ref = ref->next;
}
}
}
可以了解到,在renamerefs中,如果进行了track当前对象,那么就会对当前的ref_entry也就是log的日志进行操作。
printrefs
而在track了之后,我们怎么才能够把对应的信息打印出来呢,refbase提供了printrefs的方法,来将对应的log信息保存到对应目录
void printRefs() const
{
String8 text;
{
Mutex::Autolock _l(mMutex);
char buf[128];
snprintf(buf, sizeof(buf),
"Strong references on RefBase %p (weakref_type %p):\n",
mBase, this);
text.append(buf);
printRefsLocked(&text, mStrongRefs); // 将强引用进行收集
snprintf(buf, sizeof(buf),
"Weak references on RefBase %p (weakref_type %p):\n",
mBase, this);
text.append(buf);
printRefsLocked(&text, mWeakRefs);// 将弱引用进行收集
}
{
char name[100];
snprintf(name, sizeof(name), DEBUG_REFS_CALLSTACK_PATH "/%p.stack",
this);
int rc = open(name, O_RDWR | O_CREAT | O_APPEND, 644);
if (rc >= 0) {
(void)write(rc, text.string(), text.length()); // 保存到/data/debug/文件夹中
close(rc);
ALOGD("STACK TRACE for %p saved in %s", this, name);
}
else ALOGE("FAILED TO PRINT STACK TRACE for %p in %s: %s", this,
name, strerror(errno));
}
}
void printRefsLocked(String8* out, const ref_entry* refs) const
{
char buf[128];
while (refs) {
char inc = refs->ref >= 0 ? '+' : '-';
snprintf(buf, sizeof(buf), "\t%c ID %p (ref %d):\n",
inc, refs->id, refs->ref);
out->append(buf);
#if DEBUG_REFS_CALLSTACK_ENABLED
out->append(CallStack::stackToString("\t\t", refs->stack.get()));
#else
out->append("\t\t(call stacks disabled)");
#endif
refs = refs->next;
}
}
可以看到,printrefs是将该对象的强引用和弱引用都进行了打印,同时会将log信息保存到/data/debug/目录下,这样就可以获取到关注对象的具体调用信息了。
总结:
调试RefBase的一般步骤
1.编译debug版本的libutils
2.在需要关注的对象前加上trackme
3.在需要堆栈信息的地方加上printrefs
4.在data/debug/目录下获取到对应的log