使用MDB查看變量的值

本節描述使用MDB查看core文件中變量的基本知識

基本概念:

一般程序發生coredump,80%的可能是由於參數的值不對造成的(其他可能是堆棧溢出、多線程等問題造成的)。對於可以復現的問題,一般拿到函數堆棧,通過走讀代碼基本上就可以定位。對於無法復現的問題,大部分情況需要通過查看變量的值來定位。(注:網上很多文章都是介紹通過反彙編定位哪條語句引起的coredump,個人認爲這種方法比較繁瑣(需要重新編譯),且對優化後的程序用處不大,本文就不討論了。)

變量可以分爲兩類,一類是基本類型,例如int等,可以在函數堆棧裏直接看到值;另一類是類類型,包括string,STL和用戶自定義類型等,這種類型在函數堆棧裏會顯示它的地址。

使用"地址/格式"這種形式,可以查看地址中保存的值。例如,有代碼:

int *p = new int(2010);

假設p=0x8011320,則使用8011320/D,就可以顯示2010。常用的格式如下:

X:十六進制int(一般用來顯示地址)

D:十進制有符號int

U:十進制無符號int

E:十進制無符號long long

e:十進制有符號long long

s:顯示字符串

查看變量的值時,一定要有源代碼(至少要有函數聲明),否則就無法知道變量的類型,許多地址取值操作就無法實施。

實例:

OS:Solaris10(x86)

編譯器:Sun Studio 11

1) 看實參的值,普通函數,參數爲內置類型(int類型)。

有如下程序:

view plaincopy to clipboardprint?
void f(int i)
{
throw 0;
}

int main()
{
int i = 1;
f(i);
return 0;
}
void f(int i)
{
throw 0;
}

int main()
{
int i = 1;
f(i);
return 0;
}

編譯(CC -o 1 coretest.cpp)、運行,會產生core文件。

使用MDB:

bash-3.00# mdb core

Loading modules: [ libc.so.1 ld.so.1 ]

> $G

C++ symbol demangling enabled

> $C

080471f4 libc.so.1`_lwp_kill+7(1, 6)

0804720c libc.so.1`raise+0x1f(6)

08047254 libc.so.1`abort+0xcd(fee28ef0, a8, 804727c, fee14653, fee28ef0,

fee29a20)

08047264 libCrun.so.1`void __Cimpl::default_terminate+0x18(fee28ef0, fee29a20,

80472ac, fefc0568, 80472ac, fee14ab9)

0804727c libCrun.so.1`void std::terminate+0x1b(0, fee29804, fee28ef0, 28,

fee1537d, 0)

080472ac libCrun.so.1`void __Cimpl::ex_terminate+0x39(8047440, fee29804,

fee28ef0, 0, 805092a, 80472e4)

080472c8 libCrun.so.1`_ex_throw_body+0x79(fee29a20, 0)

080472e4 libCrun.so.1`void __Crun::ex_throw+0x52(fee29a6c, 8050b78, 0)

0804730c void f+0x37(1)

0804732c main+0x1f(1, 8047358, 8047360)

0804734c _start+0x7a(1, 80474a8, 0, 80474ac, 80474e0, 80474f5)

其中1即爲參數i的值。

2) 看實參的值,普通函數,參數爲類類型。

有如下程序:

view plaincopy to clipboardprint?
class A
{
public:
int m_i;
int m_j;
};

void f(const A& a)
{
throw 0;
}

int main()
{
A a;
a.m_i = 3;
a.m_j = 7;
f(a);
return 0;
}
class A
{
public:
int m_i;
int m_j;
};

void f(const A& a)
{
throw 0;
}

int main()
{
A a;
a.m_i = 3;
a.m_j = 7;
f(a);
return 0;
}

編譯(CC -o 1 coretest2.cpp)、運行,會產生core文件。

使用MDB:

bash-3.00# mdb core

Loading modules: [ libc.so.1 ld.so.1 ]

> $G

C++ symbol demangling enabled

> $C

080471f4 libc.so.1`_lwp_kill+7(1, 6)

0804720c libc.so.1`raise+0x1f(6)

08047254 libc.so.1`abort+0xcd(fee28ef0, a8, 804727c, fee14653, fee28ef0,

fee29a20)

08047264 libCrun.so.1`void __Cimpl::default_terminate+0x18(fee28ef0, fee29a20,

80472ac, fefc0568, 80472ac, fee14ab9)

0804727c libCrun.so.1`void std::terminate+0x1b(0, fee29804, fee28ef0, 28,

fee1537d, 0)

080472ac libCrun.so.1`void __Cimpl::ex_terminate+0x39(8047440, fee29804,

fee28ef0, 0, 805092a, 80472e0)

080472c8 libCrun.so.1`_ex_throw_body+0x79(fee29a20, 0)

080472e0 libCrun.so.1`void __Crun::ex_throw+0x52(fee29a6c, 8050b80, 0)

08047308 void f+0x37(8047320)

0804732c main+0x26(1, 8047358, 8047360)

0804734c _start+0x7a(1, 80474a8, 0, 80474ac, 80474e0, 80474f5)

因爲函數原型爲void f(const A& a),a爲引用類型,所以8047320爲a的地址(16進制),即m_i的地址,所以m_i的值爲:

> 8047320/X

0x8047320: 3

m_j的地址爲m_i的地址加4,所以m_j的值爲:

> 8047320+4/X

0x8047324: 7

3) 看實參的值,普通函數,參數爲類類型(有虛函數表)。

有如下程序:

view plaincopy to clipboardprint?
class A
{
public:
virtual ~A(){}
int m_i;
int m_j;
};

void f(const A& a)
{
throw 0;
}

int main()
{
A a;
a.m_i = 3;
a.m_j = 7;
f(a);
return 0;
}
class A
{
public:
virtual ~A(){}
int m_i;
int m_j;
};

void f(const A& a)
{
throw 0;
}

int main()
{
A a;
a.m_i = 3;
a.m_j = 7;
f(a);
return 0;
}

編譯(CC -o 1 coretest3.cpp)、運行,會產生core文件。

使用MDB:

bash-3.00# mdb core

Loading modules: [ libc.so.1 ld.so.1 ]

> $G

C++ symbol demangling enabled

> $C

080471e4 libc.so.1`_lwp_kill+7(1, 6)

080471fc libc.so.1`raise+0x1f(6)

08047244 libc.so.1`abort+0xcd(fee28ef0, a8, 804726c, fee14653, fee28ef0,

fee29a20)

08047254 libCrun.so.1`void __Cimpl::default_terminate+0x18(fee28ef0, fee29a20,

804729c, fefc0568, 804729c, fee14ab9)

0804726c libCrun.so.1`void std::terminate+0x1b(0, fee29804, fee28ef0, 28,

fee1537d, 0)

0804729c libCrun.so.1`void __Cimpl::ex_terminate+0x39(8047440, fee29804,

fee28ef0, 0, 8050a4a, 80472d8)

080472b8 libCrun.so.1`_ex_throw_body+0x79(fee29a20, 0)

080472d8 libCrun.so.1`void __Crun::ex_throw+0x52(fee29a6c, 8050d58, 0)

08047300 void f+0x37(804731c)

0804732c main+0x32(1, 8047358, 8047360)

0804734c _start+0x7a(1, 80474a8, 0, 80474ac, 80474e0, 80474f5)

因爲函數原型爲void f(const A& a),a爲引用類型,所以804731c爲a的地址(16進制)。因爲類型A含有虛函數,所以804731c指向的內容8060fa4爲虛函數表的地址:

> 804731c/X

0x804731c: 8060fa4

> 8060fa4/X

1`__1cBAG__vtbl_:

1`A::__vtbl: 8050d80

> 8060fa4::nm

Value Size Type Bind Other Shndx Name

0x08060fa4|0x0000000c|OBJT |WEAK |0x0 |18 |1`__1cBAG__vtbl_

m_i的地址爲a的地址加4,所以m_i的值爲:

> 804731c+4/X

0x8047320: 3

m_j的地址爲m_i的地址加4,所以m_j的值爲:

> 804731c+4+4/X

0x8047324: 7

4) 看實參的值,成員函數,參數爲類類型(有虛函數表)。

有如下程序:

view plaincopy to clipboardprint?
class A
{
public:
virtual ~A(){}
int m_i;
int m_j;
};

class B
{
public:
void f(const A& a)
{
throw 0;
}
};

int main()
{
A a;
a.m_i = 3;
a.m_j = 7;
B b;
b.f(a);
return 0;
}
class A
{
public:
virtual ~A(){}
int m_i;
int m_j;
};

class B
{
public:
void f(const A& a)
{
throw 0;
}
};

int main()
{
A a;
a.m_i = 3;
a.m_j = 7;
B b;
b.f(a);
return 0;
}

編譯(CC -o 1 -g coretest4.cpp)、運行,會產生core文件。

使用MDB:

bash-3.00# mdb core

Loading modules: [ libc.so.1 ld.so.1 ]

> $G

C++ symbol demangling enabled

> $C

080471e4 libc.so.1`_lwp_kill+7(1, 6)

080471fc libc.so.1`raise+0x1f(6)

08047244 libc.so.1`abort+0xcd(fee28ef0, a8, 804726c, fee14653, fee28ef0,

fee29a20)

08047254 libCrun.so.1`void __Cimpl::default_terminate+0x18(fee28ef0, fee29a20,

804729c, fefc0568, 804729c, fee14ab9)

0804726c libCrun.so.1`void std::terminate+0x1b(0, fee29804, fee28ef0, 28,

fee1537d, 0)

0804729c libCrun.so.1`void __Cimpl::ex_terminate+0x39(8047440, fee29804,

fee28ef0, 0, 8050a4a, 80472d0)

080472b8 libCrun.so.1`_ex_throw_body+0x79(fee29a20, 0)

080472d0 libCrun.so.1`void __Crun::ex_throw+0x52(fee29a6c, 8050d94, 0)

080472f8 void B::f+0x37(804731b, 804731c)

0804732c main+0x39(1, 8047358, 8047360)

0804734c _start+0x7a(1, 80474a8, 0, 80474ac, 80474e0, 80474f5)

因爲f爲非靜態成員函數,所以第一個參數(804731b)爲this指針,第二個參數(804731c)爲a的地址,其他變量查看方法同3)

注:如果f爲靜態成員函數,則不存在this指針。


本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/lw1a2/archive/2009/10/06/4636469.aspx


使用MDB查看變量的值(2)

[email protected]

本節描述使用MDB查看core文件中STL變量的知識

一、目的

在《使用MDB查看變量的值(1)》中,我們 探討了查看變量值的一般方法,但是對於複雜的對象,一點一點的查看內存太麻煩,MDB提供一種機制,可以自己實現插件來解析內存中的變量。

二、原理

在《Solaris 模塊調試器指南(819-7055-10)》的第十章詳細的介紹了編寫插件的方法。這裏只簡單介紹下幾個重要的函數。

1) const mdb_modinfo_t *_mdb_init(void);

mdb插件的初始化函數,返回一個mdb_modinfo_t結構的指針,mdb_modinfo_t的定義如下:

typedef struct mdb_modinfo {

ushort_t mi_dvers; /* Debugger API version number */

const mdb_dcmd_t *mi_dcmds; /* NULL-terminated list of dcmds */

const mdb_walker_t *mi_walkers; /* NULL-terminated list of walks */

} mdb_modinfo_t;

l mi_dvers表示版本號,應該始終設置爲MDB_API_VERSION

l mi_dcmds指向自定義dcmd命令的數組

l mi_walkers指向自定義walker命令的數組。

mi_dcmds的例子:

static const mdb_dcmd_t mi_dcmds [] = {

{ "plist", NULL, "print list", plist }, //自定義dcmd命令plist

{ NULL }

};

注:本文只介紹自定義dcmd命令的實現

2) void _mdb_fini(void);

自定義插件卸載時(::unload)執行的操作。

3) int dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv);

自定義dcmd命令的實現:

例如上面的mi_dcmds的例子中,就需要定義應該這樣的函數:

int plist (uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv);

l addr:0x804792c::plist,這樣調用plist時,addr就等於0x804792c

l flags:標誌位,其中flags & DCMD_ADDRSPEC爲真時,表示以addr::plist這樣的形式調用自定義dcmd

l argc:參數個數,如0x804792c::plist int,參數個數就爲1

l argv:參數值,如0x804792c::plist int,參數值就爲int

4) ssize_t mdb_vread(void *buf, size_t nbytes, uintptr_t addr);

從給定的目標虛擬地址addr開始,取長度爲nbytes的一塊內存值,將其賦給buf。例如:

0x804792c/D對應的mdb_vread爲mdb_vread(buf, sizeof(int), 0x804792c)

5) ssize_t mdb_readstr(char *s, size_t nbytes, uintptr_t addr);

從給定的目標虛擬地址addr開始,以空字符結尾的C 字符串讀入由s 尋址的緩衝區中。主要用來讀取內存中的字符串,例如:

0x804792c/s對應的mdb_readstr爲mdb_readstr(s, 255, 0x804792c),其中255爲緩衝區最大值。

6) void mdb_printf(const char *format, ...);

類似於printf,將計算完的值格式化打印到屏幕上。

三、實例

OS:Solaris10(x86)

編譯器:Sun Studio 11

在/usr/demo/mdb下有一個MDB插件的例子,本實例根據這個例子改編而來,實現了查看std::list、std::vector、std::map、std::set的值。對於以上四種stl容器,除了可以以地址形式打印成員變量外,還支持以int、long long、string這三種類型來打印成員變量。

完整代碼:

http://download.csdn.net/source/2002961

四、詳細說明

1) list

stl的list使用雙向鏈表來實現,通過dbx的print -r,可以顯示出list的結構,其僞結構如下(每個變量都是指針):

{

__buffer_size,

__buffer_list,

__free_list,

__next_avail,

__last,

__node, //指向list的node的指針

__length //list含有的成員個數

}

list由多個__node組成,__node的僞結構:

{

next, //雙向鏈表中,指向下一個節點的指針

prev, //雙向鏈表中,指向上一個節點的指針

data //指向list成員的指針,本程序就是打印這個指針

}

假設程序裏list變量的地址爲0x804792c,則:

l __length的地址爲0x804792c+sizeof(uintptr_t)*6,由此可以計算出list成員的個數;

l __node的地址爲0x804792c+sizeof(uintptr_t)*5,由此可以計算出每個list成員的地址。__node僞結構中的data即爲每個成員的值(或指針)。通過next指針,可以遍歷整個雙向鏈表。

使用方法:

將上面的壓縮包解壓開,執行make,solaris會根據系統的CPU編譯不同的動態庫,筆者的系統是x86系統,所有會在i386目錄下生成printstl.so。可以將這個動態庫複製到/usr/lib/mdb/proc/目錄下(MDB插件默認目錄),然後在mdb中使用::load printstl.so加載插件,使用::unload printstl.so卸載插件。如果不將動態庫複製到插件的默認目錄,則需要使用絕對路徑加載插件:::load /XXX/printstl.so。

具體使用方法:

l 804792c::plist:以指針形式打印list,例:

> 804792c::plist

The list size is: 3

The list member is: ([0]0x8079bf0, [1]0x8099978, [2]0x8099988)

l 804792c::plist int:知道list裏保存的是int類型,打印list,例:

> 804792c::plist int

The list size is: 3

The list member is: ([0]123, [1]456, [2]789)

l 804792c::plist long long:知道list裏保存的是long long類型,打印list,例:

> 804792c::plist long long

The list size is: 3

The list member is: ([0] 9223372036854775807, [1]456, [2]789)

l 804792c::plist string:知道list裏保存的是string類型,打印list,例:

> 8047804::plist string

The list size is: 3

The list member is: ([0]abc, [1]def, [2]Good)

2) vector

stl的vector中的成員保存在一塊連續的內存中,如果能得到成員變量的開始地址、結束地址和成員的大小,就能確定每個成員的地址。通過dbx的print -r,可以顯示出vector的結構,其僞結構如下(每個變量都是指針):

{

__buffer_size,

__start, //成員變量的開始地址

__finish, //成員變量的結束地址

__end_of_storage

}

假設程序裏vector變量的地址爲0x8047900,則:

l __start的地址爲0x8047900+sizeof(uintptr_t);

l __finish的地址爲0x8047900+sizeof(uintptr_t)*2。

使用方法同list。

注:若無法確定成員的大小,則只能打印出開始地址和結束地址。

3) map

stl的map內部使用紅黑樹實現。通過dbx的print -r,可以顯示出map的結構,其僞結構如下(每個變量都是指針):

{

__buffer_size,

__buffer_list,

__free_list,

__next_avail,

__last,

__header, //指向紅黑樹的head的指針

__node_count, //含有的成員個數

__insert_always,

__key_compare

}

map節點,僞結構如下:

{

color_field,

parent_link,

left_link.

right_link,

first, //map中的key,

second //map中的value

}

假設程序裏map變量的地址爲0x8047830,則:

l __node_count的地址爲0x8047830+sizeof(uintptr_t)*6;

l __header的地址爲0x8047830+sizeof(uintptr_t)*5,之後就可以得到first和second;

l 得到紅黑樹的head指針後,就可以使用遍歷二叉樹的方法來遍歷。使用中序遍歷二叉樹的方法,可以將map中的成員按照從小到大的順序打印出。

使用方法:

l 8047830::pmap int, int:知道map的定義爲map,打印map,例:

> 8047830::pmap int, int

The map size is: 3

The map member is: ([0](8,4000), [1](9,2000), [2](1999,2000))

其他使用方法可參考list

4) set

stl的set內部也是使用紅黑樹實現,只不過set節點的結構有所不同:

{

color_field,

parent_link,

left_link,

right_link,

value_field //set中的key

}

set中只有一個key,其他的地方都和map相同。

使用方法同list

五、參考資料:

《Solaris 模塊調試器指南(819-7055-10)》

《STL源碼剖析》


本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/lw1a2/archive/2010/01/17/5203645.aspx


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章