2010年11月16日 星期二

vector與map...怎麼選擇

平常我們在寫程式時,經常可以用很多方法來解決
不過寫到後來,可能會因為客戶要求, 程式品質, 或者挑戰自己
而想要用比較有效率的方法
這時候,什麼是有效率的,沒比較過是不會知道的
我們在寫C++程式時,一定都知道STL,常用的排序與尋找的方法
就是vector 搭配binary_search 以及map這二個較多了

以下的程式碼(BCB5),就把這2個做了一個比較(200萬筆)
//---------------------------------------------------------------------------

#include < vcl.h >
#pragma hdrstop
#include < iostream >
#include < vector >
#include < list >
#include < map >
#include < ctime >
#include < stdio.h >
#include < time.h >
//---------------------------------------------------------------------------
#pragma argsused

using namespace std;

int main(int argc, char* argv[])
{

//vector
vector v;
vector ::iterator it;

v.push_back( 10 );

cout << "vector container " << endl << "================" << endl << endl;
time_t t1; time(&t1);
for(int i=0;i<2000000;i++)
v.push_back(i);
// v.insert( v.begin( ) , i );
time_t t2; time(&t2);
cout<<" vcetor insert data , the time it takes is " << t2-t1 << " sec." << endl;

sort(v.begin(),v.end());

int a;
time_t t5; time(&t5);
for(int i=0;i<2000000;i++)
{
srand(time(NULL));
a=(rand() % 2000000) +1;
bool b1 = binary_search(v.begin(),v.end(),a);
}
time_t t6; time(&t6);
cout<<" vcetor search data , the time it takes is " << t6-t5 << " sec." << endl;


time_t t3; time(&t3);
v.erase (v.begin(),v.begin()+2000000);
time_t t4; time(&t4);
cout <<" vcetor erase data , the time it takes is " << t4-t3 << " sec." << endl ;
//map
map< int,int > mymap;
map< int,int >::iterator map_it;
pair< map< int,int >::iterator,bool > ret;


mymap.insert ( pair< int,int >(0,100) );

cout << endl << "map container " << endl << "================" << endl;
time_t map_t1; time(&map_t1);
for(int i=1;i<2000000;i++)
mymap.insert ( pair(i,2000000+i) );
time_t map_t2; time(&map_t2);
cout << endl <<" map insert data , the time it takes is " << map_t2-map_t1 << " sec." << endl;


int c;
time_t map_t5; time(&map_t5);
for(int i=0;i<2000000;i++)
{
srand(time(NULL));
c=(rand() % 2000000) +1;
map_it=mymap.find(c);
}
time_t map_t6; time(&map_t6);
cout<<" map search data , the time it takes is " << map_t6-map_t5 << " sec." << endl;



time_t map_t3; time(&map_t3);
mymap.erase(mymap.begin(),mymap.end());
time_t map_t4; time(&map_t4);
cout <<" map erase data , the time it takes is " << map_t4-map_t3 << " sec." << endl << endl;

system("pause");
return 0;
}
//---------------------------------------------------------------------------

以下是執行結果,可以明顯看出,vector與binary_search的組合是比較有效率的
你不禁會懷疑,是不是巧合(因為random),執行幾次後,還是一樣

vector container
================

vcetor insert data , the time it takes is 0 sec.
vcetor search data , the time it takes is 4 sec.
vcetor erase data , the time it takes is 0 sec.

list container
================

list insert data , the time it takes is 0 sec.

list erase data , the time it takes is 1 sec.

map container
================

map insert data , the time it takes is 8 sec.
map search data , the time it takes is 5 sec.
map erase data , the time it takes is 1 sec.

請按任意鍵繼續 . . .

從上面的比較可以得知,vector與binary_search的合作似乎都比較好
那為什麼還要用到map,答案隱藏在上面vector程式碼裡用雙斜線mark掉那行
如果我vector都一直insert在第1筆,那效率就很慘了(可以打開來試看看)
況且,如果你的情況,是要一邊insert , 排序, search
那猜測這樣的結果可能不見得會是vector跟binary_search會比較好了(因為不是push_back)
筆者試過,每push_back 1筆就排序一次,只要2萬筆,...就慢到不行

所以建議:
(1)如果資料一剛開始就確定範圍,可以用vector 與binary_search來處理
(2)如果資料使用中不斷變化,簡單方法可以用map
(3)承(2),如果一定要用vector,比較常遇到的解法是
1)不要用sort,改用lower_bound或upper_bound來決定插入資料的位置
2)因為sort資料會花時間,尤其是資料量大時,如果還是要用sort,那就原始資料一律用vector.push_back,
另外建一個vector來與原始資料對應序號(KEY),針對這個KEY的vector進行SORT
會有效率得多,不過在做erase時,請記得2方都要刪除
(當然KEY的vector也可運用1)的技巧)

以下補充:以下參考(http://hi.baidu.com/litinglong2010/blog/item/6b55d23962cbabca9f3d6215.html)

vector中存儲資料 標準關聯容器(map)
概要:在有序vector中存儲資料很有可能比在標準關聯容器中保存相同的資料消耗更少的記憶體;
當頁面錯誤值得重視的時候,在有序vector中通過二分法查找可能比在一個標準關聯容器中查找更快。

當然,有序vector的大缺點是它必須保持有序!當一個新元素插入時,大於這個新元素的所有東西都必須向上移一位。
它和聽起來一樣昂貴,如果vector必須重新分配它的內在記憶體(參見條款14),則會更昂貴,
因為vector中所有的元素都必須拷貝。同樣的,如果一個元素從vector中被刪除,所有大於它的元素都要向下移動。
vector的插入和刪除都很昂貴,但是關聯容器的插入和刪除則很輕量。這就是為什麼只有當你知道
你的資料結構使用的時候查找幾乎不和插入和刪除混合時,使用有序vector代替關聯容器才有意義。

本條款有很多文字,但不幸的是只有很少的例子,所以我們來看看一個使用有序vector代替set的代碼骨架:

vector < Widget > vw; // 代替set < Widget >
... // 建立階段:很多插入,
// 幾乎沒有查找
sort(vw.begin(), vw.end()); // 結束建立階段。(當
// 模擬一個multiset時,你
// 可能更喜歡用stable_sort
// 來代替;參見條款31。)
Widget w; // 用於查找的值的對象
... // 開始查找階段
if (binary_search(vw.begin(), vw.end(), w))... // 通過binary_search查找
vector < Widget > ::iterator i =
lower_bound(vw.begin(), vw.end(), w); // 通過lower_bound查找
if (i != vw.end() && !(w < *i))... // 條款19解釋了
// “!(w < *i)”測試
pair < vector < Widget > ::iterator,
vector < Widget > ::iterator > range =
equal_range(vw.begin(), vw.end(), w); // 通過equal_range查找
if (range.first != range.second)...
... // 結束查找階段,開始
// 重組階段
sort(vw.begin(), vw.end()); // 開始新的查找階段...

就像你可以看到的,這非常直截了當。裏面最難的東西就是怎麼在搜索演算法中做出選擇(比如,binary_search、lower_bound等),條款45可以幫你做出選擇。

當你決定用vector代替map或multimap時,事情會變得更有趣,因為vector必須容納pair物件。
畢竟,那是map和multimap所容納的。但是要注意,如果你聲明一個map < K, V > 的物件
(或者等價的multimap),保存在map中的元素類型是pair < const K, V > 。
如果要用vector模擬map或者multimap,你必須去掉const,因為當你對vector排序時,
它的元素的值將會通過賦值移動,那意味著pair的兩個元件都必須是可賦值的。
當使用vector來模擬map < K, V > 時,保存在vector中資料的類型將是pair < K, V > ,
而不是pair < const K, V > 。

map和multimap以順序的方式保存他們的元素,但用於排序目的時它們只作用於元素的key部分
(pair的第一個元件),所以當排序vector時你必須做一樣的事情。你需要為你的pair寫一個
自定義比較函數,因為pair的operator < 作用於pair的兩個組件。

有趣的是,你會需要第二個比較函數來進行查找。用來排序的比較函數將作用於兩個pair物件,
但是查找只用到key值。必須傳給用於查找的比較函數一個key類型的物件(要查找的值)和一個pair
(存儲在vector中的一個pair)——兩個不同的類型。還有一個附加的麻煩,你不會知道key還是pair
是作為第一個實參傳遞的,所以你真的需要兩個用於查找的比較函數:一個key值先傳遞,一個pair先傳遞。

這個例子演示了怎麼把這些東西合在一起:

typedef pair < string, int > Data; // 在這個例子裏
// "map "容納的類型
class DataCompare { // 用於比較的類
public:
bool operator()(const Data& lhs, // 用於排序的比較函數
const Data& rhs) const
{
return keyLess(lhs.first, rhs.first); // keyLess在下麵
}

bool operator()(const Data& Ihs, // 用於查找的比較函數
const Data::first_type& k) const // (形式1)
{
return keyLess(lhs.first, k);
}

bool operator()(const Data::first_type& k, // 用於查找的比較函數
const Data& rhs) const // (形式2)
{
return keyLessfk, rhs.first);
}

private:
bool keyLess(const Data::first_type& k1, // “真的”
const Data::first_type& k2) const // 比較函數
{
return k1 < k2;
}
};

在這個例子中,我們假設有序vector將模擬map < string, int > 。
這段代碼幾乎是上面討論的字面轉換,除了存在成員函數keyLess。
那個函數的存在是用來保證幾個不同的operator()函數之間的一致性。
每個這樣的函數只是簡單地比較兩個key的值,所以我們把這個測試放在keyLess中並讓operator()
函數返回keyLess所做的事情,這比複製那個邏輯要好。
這個軟體工程中絕妙的動作增強了DataCompare的可維護性,但有一個小缺點,
它提供了有不同參數類型的operator()函數,這將導致函數物件無法適配(看見條款40)。
噢,好了。

把有序vector用作map本質上和用作set一樣。唯一大的區別是必須把DataCompare物件用作比較函數:

vector < Data > vd; // 代替map < string, int >
... // 建立階段:很多插入,
// 幾乎沒有查找
sort(vd.begin(), vd.end(), DataCompare()); // 結束建立階段。(當
// 模擬multimap時,你
// 可能更喜歡用stable_sort
// 來代替;參見條款31。)
string s; // 用於查找的值的對象
... // 開始查找階段
if (binary_search(vd.begin(), vd.end(), s,
DataCompare()))... // 通過binary_search查找
vector ::iterator i =
lower_bound(vd.begin(), vd.end(), s,
DataCompare()); // 在次通過lower_bound查找,
if (i != vd.end() && !DataCompare()(s, *i))... // 條款45解釋了
// “!DataCompare()(s, *i)”測試
pair < vector < Data > ::iterator,
vector < Data > ::iterator > range =
equal_range(vd.begin(), vd.end(), s,
DataCompare()); // 通過equal_range查找
if (range.first != range.second)...
... // 結束查找階段,開始
// 重組階段
sort(vd.begin(), vd.end(), DataCompare()); // 開始新的查找階段...

正如你所見,一旦你寫了DataCompare,東西都很好的依序排列了。而一旦位置合適了,
只要你的程式按照101頁描述的階段方式使用資料結構,它們往往比相應的使用真的map的設計
運行得更快而且使用更少記憶體。如果你的程式不是按照階段的方式操作資料結構,
那麼使用有序vector代替標準關聯容器幾乎可以確定是在浪費時間。

2010年11月11日 星期四

free -- cached and buffers(2)

一般程式在執行,與進入CPU大致上會有幾個資料搬移的作業

Disk(code) <--> RAM <--> L2 Cache <--> L1 Cache(inside CPU)

當程式要執行時會愈往右邊走,執行完畢時,會慢慢走回左邊,如果都用不到,始終回到只剩DISK

以上的Cache跟主題要講的Cached有些類似,但不是指同一件事

一支程式要執行時,會有需要將使用的資料從File(Disk)讀起來使用的時候

讀起來就會進入RAM,但結束時理當要釋放這個RAM給其他程式使用

Linux考量到其他的程式可能也會用到,所以會先Cache住,如果有要用到,可以馬上反應使用

這是指在DRAM充裕的狀況下,如此運作對效能有幫助

但如果因為導致實體記憶體被吃光,而使用到Swap空間時,就輪到CPU倒楣

使用上應特別注意

其中的 Cached Memory 會依據系統的使用狀況做動態調整,借此以提高系統效能. /proc/sys/vm/drop_caches 的預設值為 0,也就是啟動 cache 的功能.如果你不喜歡這些功能或者覺得它不夠Smart的話,可以把他取消掉.在 Kernel 2.6.16 之後的本版可以透過 /proc/sys/vm/drop_caches 來釋放 Cached Memory

釋放 pagecache 所使用的 Cached Memory.



[root@localhost]# echo 1 > /proc/sys/vm/drop_caches

釋放 dentries,inodes 所使用的 Cached Memory.





[root@localhost]# echo 2 > /proc/sys/vm/drop_caches

釋放 pagecache,dentry,inode 所使用的 Cached Memory.





[root@localhost]# echo 3 > /proc/sys/vm/drop_caches




free


total used free shared buffers cached

Mem: 1023916 975816 48100 0 26376 465844

-/+ buffers/cache: 483596 540320

Swap: 2096440 105564 1990876


計算方式


total used free shared buffers cached

Mem: a b c d e f

-/+ buffers/cache: g h

Swap: i j ka = 總記憶體大小

b = 配給 buffers cache 的記憶體大小(包含未用的 buffers cache)

c = 剩下的記憶體大小

e = 配給 buffers 但未用的記憶體大小

f = 配給 cache 但未用的記憶體大小

g = buffers cache 被使用掉的記憶體大小,也就是實際被應用程式用走的

h = 那這個就是實際剩下的記憶體大小


a = b + c

a = g + h

g = b - e - f

h = c + e + f


buffer cache 的區別:

A buffer is something that has yet to be "written" to disk.

A cache is something that has been "read" from the disk and stored for later use.




一般情況下,Linux kernel 會盡可能多地利用 RAM 的空閑空間作為 cache/buffer 以最大幅度地提高系統性能。當系統中運行的應用程序占用的 RAM 增加時,則將 cache/buffer 所占用的空間釋放出來,讓渡給應用程序使用。


Mem: 那一行顯示了實際記憶體的使用率;

Swap: 顯示的是系統 swap 空間的使用率;

-/+ buffers/cache: 則是目前撥給系統緩衝區的實體記憶體數量。



記憶體管理的概觀

當系統開機一段時間後,像是「top」這種傳統的 Unix 工具常常回報少的可憐的可用記憶體數值,在我寫這篇文章的系統中,就算我總共有 512 MB 的記憶體在我的系統裡,但約開機m後三個小時,我只剩下 60 MB 的可用記憶體,那些記憶體到底跑到那裡去了?


用掉最多記憶體的地方是磁碟快取 (disk cache),目前它總共用了超過 290 MB 的記憶 ( top 裡的「cached」項目中),快取記憶體 (cached memory) 基本上是空閒的,當有新/執行中的程式需要記憶體的話,它會快速的被取回來。


為什麼 Linux 使用這麼多的記憶體來當作磁碟快取 (disk cache) 呢?主要的原因便是:假如 RAM 沒有被使用的話,它便是閒放在那邊浪費著不用。如果把資料放在用 RAM 組成的磁碟上,它的存取速度比直接從硬碟上存取還要快上 1000 倍。假如在快取裡找不到該資料,當然還是得直接從磁碟裡存取,但就如同上面說的,您將可以節省些微的存取時間。



Out of Memory

more /var/log/messages


kernel: Mem-info:

kernel: Zone:DMA freepages: 2916 min: 0 low: 0 high: 0

kernel: Zone:Normal freepages: 758 min: 766 low: 4031 high: 5791

kernel: Zone:HighMem freepages: 125 min: 253 low: 506 high: 759

kernel: Free pages: 3799 ( 125 HighMem)

kernel: ( Active: 237940/1195, inactive_laundry: 1, inactive_clean: 0, free: 3799 )

kernel: aa:0 ac:0 id:0 il:0 ic:0 fr:2916

kernel: aa:209592 ac:2087 id:1190 il:1 ic:0 fr:758

kernel: aa:26033 ac:232 id:0 il:0 ic:0 fr:125

kernel: 0*4kB 2*8kB 4*16kB 2*32kB 4*64kB 2*128kB 1*256kB 1*512kB 0*1024kB 1*2048kB 2*4096kB = 11664kB)

kernel: 8*4kB 1*8kB 175*16kB 6*32kB 0*64kB 0*128kB 0*256kB 0*512kB 0*1024kB 0*2048kB 0*4096kB = 3032kB)

kernel: 1*4kB 0*8kB 1*16kB 1*32kB 1*64kB 1*128kB 1*256kB 0*512kB 0*1024kB 0*2048kB 0*4096kB = 500kB)

kernel: Swap cache: add 5156731, delete 5156620, find 19439534/21006374, race 0+19

kernel: 4115 pages of slabcache

kernel: 1566 pages of kernel stacks

kernel: 13 lowmem pagetables, 5378 highmem pagetables

kernel: 32 bounce buffer pages, 32 are on the emergency list

kernel: Free swap: 0kB

kernel: 261760 pages of RAM

kernel: 32384 pages of HIGHMEM

kernel: 5781 reserved pages

kernel: 14591 pages shared

kernel: 116 pages swap cached

kernel: Out of Memory: Killed process 25580 (java).

free -- cached and buffers(1)

以下參考自:http://egloo.wordpress.com/2008/10/29/linux-cached-memory/
In Linux, reading from a disk is very slow compared to accessing real memory. In addition, it is common to read the same part of a disk several times during relatively short periods of time. For example, one might first read an e-mail message, then read the letter into an editor when replying to it, then make the mail program read it again when copying it to a folder. Or, consider how often the command ls might be run on a system with many users. By reading the information from disk only once and then keeping it in memory until no longer needed, one can speed up all but the first read. This is called disk buffering, and the memory used for the purpose is called the buffer cache.

Unlike (Windows) other operating systems, Linux administers memory the smartest way it can.

Since unused memory is next to worthless, the filesystem takes whatever memory is left and caches it in order to speed up disk access. When the cache fills up, the data that has been unused for the longest time is discarded and the memory thus freed is used for the new data.

Whenever an application needs memory, the kernel makes the cache smaller; You do not need to do anything to make use of the cache, it happens completely automatically.

Freeing memory buffer does not make your programs faster… Actually it makes disk access slower.

BUT if for some reason (kernel debugging for example) you want to force the buffer to be freed, you need to set the drop_caches value to 1:

$ echo 1 > /proc/sys/vm/drop_caches
Issuing this command will release all the cached memory and also will stop collecting I/O buffer blocks. Let’s see an example of the effect:




Under normal situations, most of the memory is already cached by the system. But if we force the system to free the memory, you can see in the graph how the memory is suddenly dropped.

The technical details of how this works are explained on the Linux API

2010年11月9日 星期二

Solaris process crashed debug 範例


Solaris process crashed debug 範例

DEV901 /export/spare/cts>coreadm -e process
coreadm�G�z������ root �~������ -[giedu] �ﶵ 必須用root
DEV901 /export/spare/cts>su -
Password:
#Sun Microsystems Inc. SunOS 5.8 Generic Patch December 2002
DEV901 />coreadm -e process
DEV901 />exit

DEV901 /export/spare/cts>vi crashed.c
"crashed.c" 10 ��, 139 �Ӧr��
#include
int main(int argc, char* argv[])
{
int *a=0;
printf("start...\n");
printf("%s",a); crashed 的地方

return 0;
}

~
~
"crashed.c" 10 ��, 148 �Ӧr��
-g參數確保pstack 可以查出問題所在
DEV901 /export/spare/cts>cc -o crashed crashed.c –g  
ELF的檔案類型
DEV901 /export/spare/cts>file crashed
crashed: ELF 32-�줸 MSB �i���� SPARC ���� 1, �ʺA�s��, �����h DEV901 /export/spare/cts>./crashed
start...
Segmentation Fault (core dumped)      產生core file了

DEV901 /export/spare/cts>coreadm
�����֤��ɮ׹ϼˡG/var/core/core.%f.%p
init �֤��ɮ׹ϼˡGcore.%f
�����֤߶ɦL�Gdisabled
�C�ӳB�z�֤߶ɦL�Genabled       確認會產生core file
���� setid �֤߶ɦL�Gdisabled
�C�ӳB�z setid �֤߶ɦL�Gdisabled
�����֤߶ɦL�O���Gdisabled
DEV901 /export/spare/cts>cat /etc/coreadm.conf
#
# coreadm.conf
#
# Parameters for system core file configuration.
# Do NOT edit this file by hand -- use coreadm(1) instead.
#
COREADM_GLOB_PATTERN=/var/core/core.%f.%p
COREADM_INIT_PATTERN=core.%f 產生core file檔名
COREADM_GLOB_ENABLED=no
COREADM_PROC_ENABLED=yes 確認會產生core file
COREADM_GLOB_SETID_ENABLED=no
COREADM_PROC_SETID_ENABLED=no
COREADM_GLOB_LOG_ENABLED=no


DEV901 /export/spare/cts>ls -l core.crashed
-rw------- 1 cts staff 80360 11�� 9 11:16 core.crashed
DEV901 /export/spare/cts>pstack core.crashed
core 'core.crashed' of 23061: ./crashed
這個位置發生core dump
ff2b3218 strlen (0, ffbefad0, 0, ff33f789, 0, 10702) + 80
ff308188 printf (10700, ff340284, ff343a54, 0, 22318, ff29bc20) + f4
000106a8 main (1, ffbefb54, ffbefb5c, 20800, 0, 0) + 30
00010650 _start (0, 0, 0, 0, 0, 0) + 108
DEV901 /export/spare/cts>pmap core.crashed
core 'core.crashed' of 23061: ./crashed
00010000 8K read/exec /export/spare/cts/crashed
00020000 8K read/write/exec /export/spare/cts/crashed
相對於symbol table的位置,應該在FF280000之後
FF280000 688K read/exec /usr/lib/libc.so.1
FF33C000 32K read/write/exec /usr/lib/libc.so.1
FF380000 8K read/write/exec /usr/lib/libdl.so.1
FF390000 8K read/exec /usr/platform/sun4u-us3/lib/libc_psr.so.1
FF3A0000 176K read/exec /usr/lib/ld.so.1
FF3DC000 8K read/write/exec /usr/lib/ld.so.1
FF3DE000 8K read/write/exec /usr/lib/ld.so.1
FFBEE000 8K read/write/exec [ stack ]
total 952K

DEV901 /export/spare/cts>elfdump -s /usr/lib/libc.so.1 | grep strlen
[1965] 0x00033198 0x000000f0 FUNC GLOB D 30 .text strlen
[723] 0x00000000 0x00000000 FILE LOCL D 0 ABS strlen.s
[2129] 0x0007c6b4 0x0000002c FUNC LOCL D 0 .text mini_strlen
[4677] 0x00033198 0x000000f0 FUNC GLOB D 0 .text strlen
0xFF280000 + 0x00033198 + 80 = 0xFF2b3218
從上面可以看到,strlen crashed的位置是如何被找出來的。從pstack可以得到0xFF2b3218,透過pmap找出symbol table中,crashed的base address應該落於0xFF280000(屬於libc.so.1),因為libc.so.1以及./crashed都是ELF檔案,所以用elfdump找出其中的symbol位置,最後發現在0x0033198。而因為pstack有再記載crashed address是位在其base address所在symbol再+0x80之偏移量,所以算出這樣結果。(也就是global strlen()是發生問題所在,但並不表示他有BUG)

Linux process crashed debug 範例

Linux process crashed debug 範例
[root@HTS099 ~]# cat crashed.c
#include
int main(int argc, char* argv[])
{
int *a=0;
printf("start...\n");
*a= 1;

return 0;
}

[root@HTS099 ~]# gcc -o crashed crashed.c -g
[root@HTS099 ~]# ./crashed
start...
Segmentation fault

[root@HTS099 ~]# ulimit -c
0
[root@HTS099 ~]# ulimit -c unlimited
[root@HTS099 ~]# ulimit -c
unlimited
[root@HTS099 ~]# ./crashed
start...
Segmentation fault (core dumped)

[root@HTS099 ~]# ls -l core.*
-rw------- 1 root root 143360 Nov 9 10:06 core.31393

[root@HTS099 ~]# gdb ./crashed core.31393
GNU gdb Fedora (6.8-37.el5)
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...

warning: Can't read pathname for load map: Input/output error.
Reading symbols from /lib/libc.so.6...done.
Loaded symbols for /lib/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
Core was generated by `./crashed'.
Program terminated with signal 11, Segmentation fault.
[New process 31393]
#0 0x080483ab in main () at crashed.c:6
6 *a= 1;
(gdb) bt
#0 0x080483ab in main () at crashed.c:6
(gdb) list *0x080483ab
0x80483ab is in main (crashed.c:6).
1 #include
2 int main(int argc, char* argv[])
3 {
4 int *a=0;
5 printf("start...\n");
6 *a= 1;
7
8 return 0;
9 }

Core & Dump @ Solaris (2)


pstack, coreadm and symbol tables
Where symbol names come from?
In ELF files, symbols reside in two sections: .symtab and .dynsym.

On recent versions of Solaris, there is a new section, .SUNW_ldynsym, but for the purpose of this article it is identical to .dynsym, so I'll keep it simple and not talk about it.

Both sections are essentially tables that map name to a value; here we are interested in function names, so that value would be function address. When pstack unwinds the stack (starting from value of $pc and $fp/$sp registers that comes from special NOTE segment of core file), it goes through symbol tables of all files involved and find symbol with closest value.

For example, suppose we have this core file:

$ pstack core
core 'core' of 7719: ./a.out
fece586c strlen (8050ada, 8047a38, fed91c20, 0) + c
fed40814 printf (8050ad8, 0) + a8
08050969 ???????? (0, 8047b30, 8047a84, 80508bd, 1, 8047a90)
080509a2 main (1, 8047a90, 8047a98, fed93e40) + 12
080508bd _start (1, 8047b98, 0, 8047ba0, 8047bdc, 8047be7) + 7d
fece586c address belongs to libc.so.1 as can be seen from pmap(1) output:

$ pmap core
core 'core' of 7719: ./a.out
08046000 8K rwx-- [ stack ]
08050000 4K r-x--
08060000 4K rwx--
08061000 128K rwx-- [ heap ]
>>>FECC0000 760K r-x-- /lib/libc.so.1 <<<
FED8E000 32K rw--- /lib/libc.so.1
FED96000 8K rw--- /lib/libc.so.1
...
It is in code segment (r-x-- permissions gave that away) of /lib/libc.so.1.

Looking at libc.so.1 with elfdump we can see that global function strlen starts at offset 0x25860

$ elfdump -s /usr/lib/libc.so.1 | grep strlen
[2603] 0x00025860 0x00000045 FUNC GLOB D 37 .text strlen
So in our passed away process it would reside at 0xFECC0000 (base address of libc.so.1 in memory) + 0x25860 = 0xFECE5860. Hence 0xfece586c is 0xFECE5860+0xc, which is strlen+0xc

Symbol tables
As you can see in the above example, not all symbols were found. In this case, address 0x08050969 was not mapped to any symbol. That address belongs to a.out code segment starting at 0x08050000 and that's all we can tell. Yet the other symbol from the same segment is visible: main at 0x080509a2.

The difference is because those two symbols were present in different symbol tables while executable files are permitted to have only one: .dynsym (strictly speaking, that probably applies to dynamic executables only, but since Solaris 10 strongly discourages static linking, so we almost always have to deal with dynamic executables and shared libraries). This .dynsym section is used by run-time linker (ld.so.1(1)) and contains global names that program "exports" or "imports" from libraries; call to "main" is resolved at run time by looking up name "main" in .dynsym section and jumping to address associated with symbol found. Since this information is absolutely necessary at run time, .dynsym section always resides in a loadable segment and is always a part of process memory image (and thus a core file).

On the other hand, .symtab section that contains all symbols - including local ones - was useful mostly when linking relocatable object files (*.o). References inside one file can be resolved at compile time using offsets, so static functions does not have to have a name at run time, they are called directly using offset from current position. This is why .symtab section does not belong to a loadable segment and does not contribute to process' memory image in any way. And this is why it [used to be] customary to remove symbol table from final executables (using strip(1), for example) to save space and make life of support engineers harder.

In our case, ./a.out was indeed stripped:

$ elfdump -c a.out | grep symtab
$ elfdump -c a.out | grep dynsym
Section Header[4]: sh_name: .dynsym
It does have .dynsym, but no .symtab. By the way, main symbol indeed is present in .dynsym and has address 0x08050990:

$ elfdump -s -N .dynsym a.out | grep main
[28] 0x08050990 0x0000001a FUNC GLOB D 0 .text main
Loadable objects (executables and shared libraries)
Let's recompile a.out and see how it helps:

$ CC510 a.cc
$ ./a.out
Segmentation Fault (core dumped)
$ pstack core
core 'core' of 11761: ./a.out
fece586c strlen (8050ada, 8047a38, fed91c20, 0) + c
fed40814 printf (8050ad8, 0) + a8
08050969 __1cDfoo6F_i_ (0, 8047b30, 8047a84, 80508bd, 1, 8047a90) + 19
080509a2 main (1, 8047a90, 8047a98, fed93e40) + 12
080508bd _start (1, 8047b98, 0, 8047ba0, 8047bdc, 8047be7) + 7d
We now can see name __1cDfoo6F_i_ (mangled name of int foo()) instead of ???, but where would pstack get this information? __1cDfoo6F_i_ is not present in .dynsym, so it there was not information about this name in memory image of the process when it died:

$ strings core | grep __1cDfoo6F_i_
pstack(1) is smarter that that: it finds out which program generated this core file, locates it and uses its .symtab (if present, of course) to map symbols. Here's an excerpt from proc(1):

Some of the proc tools can need to derive the name of the
executable corresponding to the process which dumped core or
the names of shared libraries associated with the process.
These files are needed, for example, to provide symbol table
information for pstack(1). If the proc tool in question is
unable to locate the needed executable or shared library,
some symbol information is unavailable for display.
Let's delete a.out and see what happens:

$ rm a.out
$ pstack core
core 'core' of 11761: ./a.out
fece586c strlen (8050ada, 8047a38, fed91c20, 0) + c
fed40814 printf (8050ad8, 0) + a8
08050969 ???????? (0, 8047b30, 8047a84, 80508bd, 1, 8047a90)
080509a2 main (1, 8047a90, 8047a98, fed93e40) + 12
080508bd _start (1, 8047b98, 0, 8047ba0, 8047bdc, 8047be7) + 7d
We immediately got our ???'s back.

So pstack uses core file and executable/libraries as well in order to print readable names in stack trace.

Core file contents
If you have to send your core file to another person for inspection, you have him at a disadvantage: that person might not have your executable and even system libraries might be slightly different. If pstack would go look for address-to-symbol mapping there, it might end up printing wrong symbol names and question marks, making core file more harmful than helpful.

There is a way to embed symbol tables into the core file - using coreadm(1M) command. It allows to specify what kind of content you want the system to put into core file and it can even direct the system to pull .symtab from executable and shared libraries:

$ coreadm -I default+symtab(do this under root).
More information on coreadm can be found in its man page: coreadm(1M).

Side note: in fact, symbol tables of libc.so.1 and ld.so.1 were present in my core file even without "symtab" content requested as can be seen by elfdump -c core; seems to be an undocumented, but useful feature.

Let's turn .symtab inclusion on and see how if it helps:

$ su -
# coreadm -I default+symtab
# exit
$ ./a.out
Segmentation Fault (core dumped)
$ rm a.out
$ pstack core
core 'core' of 13604: ./a.out
fece586c strlen (8050ada, 8047a38, fed91c20, 0) + c
fed40814 printf (8050ad8, 0) + a8
08050969 __1cDfoo6F_i_ (0, 8047b30, 8047a84, 80508bd, 1, 8047a90) + 19
080509a2 main (1, 8047a90, 8047a98, fed93e40) + 12
080508bd _start (1, 8047b98, 0, 8047ba0, 8047bdc, 8047be7) + 7d
Core file now contains many symbol tables, one per loadobject:

$ elfdump -c core | grep symtab
Section Header[1]: sh_name: .symtab
Section Header[3]: sh_name: .symtab
Section Header[6]: sh_name: .symtab
Section Header[8]: sh_name: .symtab
Section Header[10]: sh_name: .symtab
Section Header[12]: sh_name: .symtab
and one of them has definition of our int foo() function that starts at 0x08050950:

$ elfdump -s core | grep foo
[56] 0x08050950 0x00000034 FUNC LOCL D 0 __1cDfoo6F_i_
How to prevent ??? to appear in stack trace?
Use pstack on the same machine
First and foremost, you can avoid many problems by first using pstack on the same machine where core file was generated. This will ensure that pstack uses the same binary and libraries as the process that generated core. Otherwise, you might end up looking at wrong symbols or (in the best case) a lot of question marks.

Don't strip binaries
In Solaris, it is no longer customary to strip binaries (see http://blogs.sun.com/ali/entry/which_solaris_files_are_stripped). Space savings are questionable and performance of unstripped binary does not suffer, so why having lives of those who will debug it difficult?

Don't delete binaries
By default, Solaris does not include .symtab into core files (except for libc.so and ld.so as I mentioned earlier, but that is not relevant here when we talk about user executables and libraries). So if you delete or move executable/library after core file was generated, pstack won't be able to find its .symtab and thus map addresses to local function names.

In other words, unless you've changed core file contents with coreadm(1M), don't delete your binaries before you have a chance to inspect core file. They are needed.

Use coreadm
Most of problems above can be eliminated with one blow:

# coreadm -I default+symtabThis tells the system to pull .symtab sections from every binary involved in the process and put them into core file. You no longer need binaries to see names instead of numbers in stack trace.



Core & Dump @ Solaris


managing crash dumps and core files
当操作系统崩溃的时候,savecore命令在启动的时候自动执行,并将内核信息存放在/var/crash/nodename/vmcore.X文件里面,将名字列表存放在/var/crash/nodename/unix.X文件里面.
在crash dump目录下,还创建了一个bounds的文件
# dumpadm
# cat /etc/dumpadm.conf
注意尽量使用dumpadm命令而避免直接编辑/etc/dumpadm.conf文件
dumpadm的一般语法格式是:
/usr/sbin/dumpadm [nuy] [-c content-type] [-d dump-device] [-m mink| minm| min%]
[-r root-dir] [-s savecore-dir]
其中-n是禁止重启之后自动运行savecore命令,-u是执行更新,-y是允许重启之后自动运行savecore命令.content-type包括三种类型:kernel,all,curproc.-r是指定根目录,如果没有这个参数那么默认是"/"目录.
当一个core文件发生的时候,要创建两个core文件的副本,分别是global core file,per-process core file,global core file只有root用户可以操作.后一个文件只有所有者可以操作.
# coreadm
# cat /etc/coreadm.conf
带-p参数的coreadm命令任何用户都可以使用,而带其他参数的coreadm命令则只有root用户可以使用.
coreadm -i pattern命令和coreadm -p pattern命令大同小异,区别在于前者在系统重启之后才生效.coreadm -i pattern命令的作用是设定per-process core文件名模式
coreadm -e包含四项:global,process,global-setid,proc-setid,log
coreadm -d:让设定的选项失效
coreadm -u:更新
coreadm -g pattern:设定global core文件名模式

使用coreadm命令p选项的一些参数:
%u:EUID;%g:EGID;%f:可执行文件名;%n:系统标示名;%m:机器硬件名;%t:自1970.1开始运行的秒数
$$ shell's pid
使用coreadm命令的一些实例:
# coreadm -p core.%f.%p $$
# coreadm -p $HOME/corefiles/%n.%f.%p $$
# coreadm -g /var/core/core.%f.%P -e global
# coreadm 278 5678


1 Core
core檔是系統軟體出現問題後產生的資訊記錄。它對軟體發展者和系統管理者的工作有很大的幫助作用。本節主要對Core檔產生和管理做一些初步介紹。

(1) 管理core文件概述
core檔在進程或者應用程式異常終止時產生。我們可以使用coreadm命令來管理core檔。比如,你可以使用coreadm命令來設定所有的core檔都放在一個目錄中。這樣,就會很容易通過測試找到問題的痕跡。

有兩種類型的core檔:

單個進程的core檔,在默認情況下是啟動的。當進程異常終止時,單個進程的core檔就在異常終止進程的目錄中產生。產生後,單個進程的core檔的擁有者就是進程的擁有者。只有擁有者才能查看這個檔。
全局core檔,它默認的情況下是不開啟的。如果啟動了全局core檔,它產生在引起全局core檔產生的目錄。全局core文件的擁有者就是超級用戶。非特權用戶不能查看全局core檔。
當進程異常終止時,默認在當前目錄產生core檔。如果這時全局core檔路徑也開啟著,每個異常終止的進程就會有兩個檔產生:一個在當前的工作目錄;另一個在全局core檔目錄。
默認時,setuid進程既不產生全局core檔也不產生單個core檔。
如果全局core檔是開啟的,core檔可以用表17-3中的變數所描述。

表17-3 描述Core檔的變數

變 量 名 變數描述
%d 執行的檔目錄名
%f 執行的檔案名
%g 組的ID
%m 機器名稱
%n 系統節點名
%p 進程ID
%t 十進位的時間
%u 有效的用戶ID
%z 進程運行的分區(xone)名
%% 百分比


舉例說明,全局core檔路徑設置為:

/var/core/core.%f.%p

這時,如果sendmail的PID為12345的進程發生異常終止,將產生下列core檔:

/var/core/core.sendmail.12345

我們可以使用coreadm命令設定進程產生的core檔的目錄和名稱。這將取代原來的默認設置。

# coreadm -i /var/core/core.%f.%p

全局core的屬性值存儲在/etc/coreadm.conf文件中。

(2) 管理core檔的實例

1.顯示當前core檔的設置
例17-2 使用不帶任何參數的coreadm命令顯示當前core的設置情況。

$ coreadm
global core file pattern:
global core file content: default
nit core file pattern: core
init core file content: default
global core dumps: disabled
per-process core dumps: enabled
global setid core dumps: disabled
per-process setid core dumps: disabled
global core dump logging: disabled

2.設置core檔案名的樣式
將當前Shell運行的所有進程所產生的core檔命名:

$ coreadm -p $HOME/corefiles/%f.%p $$
對全局檔設置core檔命名需要超級用戶許可權:
# coreadm -g /var/corefiles/%f.%p

3.啟用單個進程core檔的路徑
# coreadm -e process
$ coreadm $$
1180: /home/kryten/corefiles/%f.%p

4.啟用全局core檔的路徑
# coreadm -e global -g /var/core/core.%f.%p

(3) 查詢core檔資訊
一些proc工具可以像檢測活動的進程一樣檢測core檔。比如,/usr/proc/bin/pstack、pmap、pldd、pflags和pcred工具能管理core文件。更詳細的資訊請查看porc(1)的幫助。

例17-3 使用proc工具檢查core檔。
$ ./a.out
Segmentation Fault(coredump)
$ /usr/proc/bin/pstack ./core
core ’./core’ of 19305: ./a.out
000108c4 main (1, ffbef5cc, ffbef5d4, 20800, 0, 0) + 1c
00010880 _start (0, 0, 0, 0, 0, 0) + b8 

2 系統crash資訊的管理

crash主要回饋主機能否正常運行等比較嚴重的問題。系統管理員需要特別關注crash資訊。
(1) 系統崩潰概述
系統崩潰(crash)發生在硬體故障、I/O問題或軟體錯誤的情況下。如果系統崩潰發生了,它將在控制臺上顯示錯誤資訊,並將實體記憶體的拷貝寫入轉儲設備。這時,系統將自動重新啟動。當系統重啟後,savecore 命令運行將資料從轉儲設備中找回,並存儲到savecore目錄。這些資料為系統診斷提供了依據。

系統崩潰轉儲檔
當savecore命令自動運行將崩潰資訊從轉儲設備中找回,並寫成兩個檔:unix.X和vmcore.X。其中X為轉儲的次序號碼。這些檔一起用於表現系統的崩潰轉儲資訊。
崩潰轉儲檔存儲在預先設定好的目錄中,默認情況下是/var/crash/hostname。在以前的Solaris版本中,崩潰轉儲檔將在系統重新啟動的時候不寫在特定的目錄,除非你手動啟動,將記憶體的映射存儲到崩潰轉儲檔中。不過現在就自動存儲崩潰轉儲檔了。

dumpadm命令
我們可以用dumpadm命令來在Solaris系統中管理系統崩潰轉儲資訊。
dumpadm命令能使你在作業系統上設置崩潰轉儲。這個命令的設置參數包括轉儲的內容、轉儲的設備和崩潰轉儲資訊的存儲路徑。
轉儲資料是以壓縮的形式被存在轉儲設備中的。內核的崩潰轉儲映射可能達到4GB,壓縮資料就意味著更快的轉儲速度和更小的轉儲設備磁碟空間。
保存崩潰轉儲檔是在後臺運行的崩潰轉儲檔的任務。系統啟動的時候不需要等待savecore命令完成就可以進行下一步。當然,大的記憶體數量是有利於savecore完成任務的。
系統崩潰轉儲檔是由savecore命令產生的,保存一般也是按默認路徑進行的。
savecore –L命粗體令是新的屬性,它使你能在系統運行的時候崩潰轉儲。當系統記憶體存儲有問題的時候,這個命令試圖在系統運行的時候調試記憶體的快照。如果系統是啟動的,並且有些命令還能執行,你可以運行cavecore –L命令來存儲系統轉儲設備的快照到崩潰轉儲目錄。

通過dumpadm命令,轉儲設置參數存儲在/etc/dumpadm.conf文件中。注意不要手動編輯這個檔,這樣做可能會帶來不一致的轉儲設置。

dumpadm命令如何工作
當系統啟動的時候,dumpadm命令調用svc:/system/dumpadm:default服務來設置基於/etc/dumpadm.conf文件的的崩潰轉儲參數。

dumpadm命令通過/dev/dump介面來初始化轉儲設備和轉儲內容。

(2) 管理系統崩潰轉儲資訊

在管理系統崩潰資訊中,你必須要記住以下幾點:
你必須是超級用戶或具有管理系統崩潰功能的用戶。
不要關閉保存崩潰轉儲資訊的屬性。系統崩潰轉儲檔提供了導致系統崩潰的很珍貴的資訊。
不要輕易刪除系統崩潰資訊。

1.如何顯示當前的崩潰轉儲設置
# dumpadm
Dump content: kernel pages
Dump device: /dev/dsk/c0t3d0s1 (swap)
Savecore directory: /var/crash/venus
Savecore enabled: yes

上面輸出的意義如下:
轉儲內容是內核記憶體的換頁。
內核記憶體將轉儲到swap設備中,就是/dev/dsk/c0t3d0s1。
系統崩潰轉儲檔存在/var/crash/venus目錄。
系統的崩潰轉儲功能是啟動著的。

2.如何修改當前的崩潰轉儲設置
命令介紹

# dumpadm -c content -d dump-device -m nnnk | nnnm | nnn% -n -s savecore-dir

dumpadm命令的各個參數具體意義列在表17-4中。

表17-4 dumpadm命令的參數列表

轉儲參數
描 述

-c content
轉儲的資料類型。默認的轉儲內容是內核的記憶體。使用all關鍵字是指所有記憶體

-d dump-device
系統崩潰時,臨時存儲的專門設備

-m nnnk | nnnm | nnn%
在savecore目錄中為了存儲崩潰轉儲檔所留的專門空間。這個參數有可能是kb(nnnk),也有可能mb(nnnm),也有可能是百分比(nnn%)

-n 或-y
是否自動進行崩潰轉儲,y為是,n為否

-s savecore-dir
用來改變崩潰轉儲檔的路徑。默認路徑是/var/crash/hostname


 例17-4 轉儲內容改為所有記憶體,轉儲目錄改為/dev/dsk/c0t1d0s1,轉儲空間最大為這個檔系統的10%。

先查看原來的轉儲設置:

# dumpadm
Dump content: kernel pages
Dump device: /dev/dsk/c0t3d0s1 (swap)
Savecore directory: /var/crash/pluto
Savecore enabled: yes

更改轉儲設置:

# dumpadm -c all -d /dev/dsk/c0t1d0s1 -m 10%
Dump content: all pages
Dump device: /dev/dsk/c0t1d0s1 (dedicated)
Savecore directory: /var/crash/pluto (minfree = 77071KB)
Savecore enabled: yes

3.如何檢查崩潰轉儲檔內容
例17-5 使用mdb工具輸出崩潰轉儲檔內容,其中包括系統資訊和在/etc/system檔中可調用的一些參數。

# /usr/bin/mdb -k unix.0
Loading modules: [ unix krtld genunix ip nfs ipc ptm ]
> ::status
debugging crash dump /dev/mem (64-bit) from ozlo
operating system: 5.10 Generic (sun4u)
> ::system
set ufs_ninode=0x9c40 [0t40000]
set ncsize=0x4e20 [0t20000]
set pt_cnt=0x400 [0t1024] 

GDB


GDB
GNU debugger
常用參數
gdb [options] [executable-file [core-file or process-id]]
gdb [options] --args executable-file [inferior-arguments ...]

GNU manual
GDB online document
Lightweight Text UI
gdbtui -- gdb 自已提供的 curses mode 介面前端
cgdb -- 提供 vim like 的程式碼檢視功能
基本操作
執行環境

程式的參數 (Program's Arguments)
set args -- 設定程式的參數
show args -- 顯示 set args 所設定的程式參數


工作目錄 (Program's Working Directory)
cd -- 改變工作目錄,和在 shell 下使用 cd 相同
pwd -- 顯示工作目錄


環境變數 (Environment Variables)
show environment -- 顯示所有環境變數的內容
show environment varname -- 顯示所指定環境變數的內容
set environment varname [=value] -- 設定環境變數
unset environment varname -- 取消環境變數設定
path DIR -- 將 DIR 加到 PATH 中
path -- 顯示 PATH 的內容


SHELL
shell command --呼叫 shell 執行外部命令。e.g shell ls

程式執行
run (r) -- 開始執行程式
continue (c) -- 離開中斷點,繼續執行程式
next (n) -- 單步執行 (step over)
step (s) -- 進入函式 (step into)
until (u) -- 離開 while, for等迴圈。 執行到程式碼的行數比目前的大,如果目前是迴圈的最後一行,就會離開迴圈
finish -- 繼續執行程式直到函式返回
start --在 main 設置暫時中斷點,並開始執行程式
advance -- 執行程式直到指定的位置
run arglist -- 同 run,並指定程式參數
start arglist -- 同 start,並指定程式參數
kill (k) --終止程式執行
quit (q) --離開 GDB
觀看程式碼
list (l) --列出目前執行程式碼前後各五行的程式碼;或是接著前次 list 的程式碼,列出之後的程式碼
"list -" -- 上次列出程式碼的前十行,類似向上翻頁
list *ADDRESS -- 列出包含指定位址的程式碼,常與 bt 配合使用
(gdb) bt
#0 0x08048405 in memory_violation () at test.cpp:9
#1 0x08048427 in main () at test.cpp:15
(gdb) list *0x08048405

directory DIR -- 新增一個路徑到程式碼搜尋路徑
show directories --顯示目前的程式碼搜尋路徑
disassemble (disas) --反組譯目前執行的程式碼
disassemble start_addr end_addr --反組譯指定範圍的程式碼。e.g. disas 0x32c4 0x32e4
設定中斷點 (breakpoint)
break 簡寫指令 b
break (b) line_number -- 在指定的行數設定中斷點
break function -- 在指定的 function 設定中斷點。e.g. break main
break filename:line_num -- 在指定檔案的指定行數設定中斷點。e.g. break main.c:10
break [LOCATION] [if CONDITION] -- 條件式中斷點,當 CONDITION 滿足時才中斷。e.g. break main.c:10 if var > 10
info break -- 列出所有的中斷點及編號
delete number -- 刪除指定編號的中斷點
disable number -- 使指定編號的中斷點失效
enable number -- 取消 disable,使指定編號的中斷點生效
設定 watchpoint
watch varname -- 設定 watch point 監看變數,當變數被寫入時,中斷程式
rwatch varname -- 設定 watch point 監看變數,當變數被讀取時,中斷程式
awatch varname -- 設定 watch point 監看變數,當變數被讀取或寫入時,中斷程式
watch *(int *)0x12345678 -- 設定 watch point 監看記憶體位址,監看範圍由變數型別決定,當此記憶體位址被寫入時,中斷程式
info watch -- 列出所有的 watchpoint
觀察變數資料
print (p) varname -- 顯示變數內容
printf expression --使用 C 語言的格式化字串 (printf format string) 功能來顯示。e.g. printf "var=%d\n", var
display varname-- 遇到中斷點時,自動顯示變數的內容
undisplay display_num -- 取消指定編號的自動顯示
info display -- 列出所有 display 及編號
whatis varname -- 顯示變數型別
ptype varname --顯示 class, struct 的定義內容
info locals -- 顯示目前的區域變數
觀察記憶體
x/8xw ADDRESS -- 印出 8 個 word (4 bytes) 的記憶體內容
x/FMT ADDRESS -- 詳細的 FMT 說明,請參考 help x
dump memory FILE START END -- 將指定範圍內的記憶體內容 dump 到檔案
顯示函式呼叫堆疊 (Call Stack)
GDB 以函式為單位分成一個個的 frame,每一個 frame 各代表一個函式。
如 main() 是一個 frame,main() 裡面所呼叫的函式是另外一個 frame。
當呼叫函式時,會把目前的 frame 推到堆疊。這個堆疊就是 frame stack,也就是一般所認知的 call stack。

backtrace (bt) --顯示目前函式呼叫堆疊 (Call Stack) 內容
backtrace full -- 一併列出區域變數 (local variable)
where --顯示目前程式的執行位置,與 backtrace 的作用相同
frame frame_num -- 跳到指定編號的 frame
up -- 往上一個 frame
down -- 往下一個 frame
info args -- 顯示傳給函式的呼叫參數
Multi-thread
info threads -- 顯示目前所有的 thread
thread thread_num -- 切換 GDB 到指定的 thread_num
改變程式行為
return -- 直接從函式目前執行位置返回,放棄未執行的部份
return expression -- 執行 return, 並傳回 expression 的值
set varname=xxx -- 更改變數值
顯示程式相關資訊
info sharedlibrary -- 顯示被載入的共享函式庫 (shared object library),及載入的記憶體位置
info registers -- 顯示基本暫存器的內容
info all-registers -- 顯示所有暫存器的內容
print $eax -- 顯示 eax 暫存器的內容
利用 core file 來幫助除錯
當程式產生 segmentation fault 時,系統會將當時的所有狀態,包括變數值,記憶體資料以及程式中各個函式的呼叫狀態 dump 成一個 core file。我們可以用 GDB 來讀取 core file,分析當時引發 segmentation fault 的原因。

下面的程式嘗試寫入記憶體位址為 0 的區域,引發系統產生 segmentation fault

#include < stdio.h >

//This function will cause "Segmentation fault"
void memory_violation()
{
char* ptr = 0;
for(int i=0; i < 10; ++i)
{
ptr[i] = i;
}
}

int main(int argc, char* argv[])
{
memory_violation();
return 0;
}

編譯指令
$ g++ -g test.cpp -o mytest

設定 core file 最大 size
core file size 預設為 0,這裡改成沒有限制
$ ulimit -c unlimited

執行程式
$ ./mytest
Segmentation fault (core dumped)

利用 GDB 讀取 core file
利用 bt 找出發生錯誤的地方
利用 list *ADDRESS 列出產生錯誤的程式碼
利用 print 觀察變數值
$ gdb ./mytest core
...
(gdb) bt
#0 0x08048405 in memory_violation () at test.cpp:9
#1 0x08048427 in main () at test.cpp:15
(gdb) list *0x08048405
0x8048405 is in memory_violation() (test.cpp:9).
4 void memory_violation()
5 {
6 char* ptr = 0;
7 for(int i=0; i<10; ++i)
8 {
9 ptr[i] = i;
10 }
11 }
12
13 int main(int argc, char* argv[])
(gdb) print ptr
$1 = 0x0



文章分類