A. 如何查看進程堆棧
這個需要用調試器才可以看到的。
linux平台,一般使用gdb
windows平台一般使用windbg
載入進程後,可以在堆棧窗口看到堆棧的內容的。
B. 為什麼要用堆棧,什麼是堆棧
粘帖一個:
堆(heap)和棧(stack)有什麼區別??
簡單的可以理解為:
heap:是由malloc之類函數分配的空間所在地。地址是由低向高增長的。
stack:是自動分配變數,以及函數調用的時候所使用的一些空間。地址是由高向低減少的。
預備知識—程序的內存分配
一個由c/C++編譯的程序佔用的內存分為以下幾個部分
1、棧區(stack)— 由編譯器自動分配釋放 ,存放函數的參數值,局部變數的值等。其操作方式類似於數據結構中的棧。
2、堆區(heap) — 一般由程序員分配釋放, 若程序員不釋放,程序結束時可能由OS回收 。注意它與數據結構中的堆是兩回事,分配方式倒是類似於鏈表,呵呵。
3、全局區(靜態區)(static)—,全局變數和靜態變數的存儲是放在一塊的,初始化的全局變數和靜態變數在一塊區域, 未初始化的全局變數和未初始化的靜態變數在相鄰的另一塊區域。 - 程序結束後有系統釋放
4、文字常量區 —常量字元串就是放在這里的。 程序結束後由系統釋放
5、程序代碼區—存放函數體的二進制代碼。
二、例子程序
這是一個前輩寫的,非常詳細
//main.cpp
int a = 0; 全局初始化區
char *p1; 全局未初始化區
main()
{
int b; 棧
char s[] = "abc"; 棧
char *p2; 棧
char *p3 = "123456"; 123456在常量區,p3在棧上。
static int c =0; 全局(靜態)初始化區
p1 = (char *)malloc(10);
p2 = (char *)malloc(20);
分配得來得10和20位元組的區域就在堆區。
strcpy(p1, "123456"); 123456放在常量區,編譯器可能會將它與p3所指向的"123456"優化成一個地方。
}
二、堆和棧的理論知識
2.1申請方式
stack:
由系統自動分配。 例如,聲明在函數中一個局部變數 int b; 系統自動在棧中為b開辟空間
heap:
需要程序員自己申請,並指明大小,在c中malloc函數
如p1 = (char *)malloc(10);
在C++中用new運算符
如p2 = (char *)malloc(10);
但是注意p1、p2本身是在棧中的。
2.2
申請後系統的響應
棧:只要棧的剩餘空間大於所申請空間,系統將為程序提供內存,否則將報異常提示棧溢出。
堆:首先應該知道操作系統有一個記錄空閑內存地址的鏈表,當系統收到程序的申請時,
會遍歷該鏈表,尋找第一個空間大於所申請空間的堆結點,然後將該結點從空閑結點鏈表中刪除,並將該結點的空間分配給程序,另外,對於大多數系統,會在這塊內存空間中的首地址處記錄本次分配的大小,這樣,代碼中的delete語句才能正確的釋放本內存空間。另外,由於找到的堆結點的大小不一定正好等於申請的大小,系統會自動的將多餘的那部分重新放入空閑鏈表中。
2.3申請大小的限制
棧:在Windows下,棧是向低地址擴展的數據結構,是一塊連續的內存的區域。這句話的意思是棧頂的地址和棧的最大容量是系統預先規定好的,在 WINDOWS下,棧的大小是2M(也有的說是1M,總之是一個編譯時就確定的常數),如果申請的空間超過棧的剩餘空間時,將提示overflow。因此,能從棧獲得的空間較小。
堆:堆是向高地址擴展的數據結構,是不連續的內存區域。這是由於系統是用鏈表來存儲的空閑內存地址的,自然是不連續的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限於計算機系統中有效的虛擬內存。由此可見,堆獲得的空間比較靈活,也比較大。
2.4申請效率的比較:
棧由系統自動分配,速度較快。但程序員是無法控制的。
堆是由new分配的內存,一般速度比較慢,而且容易產生內存碎片,不過用起來最方便.
另外,在WINDOWS下,最好的方式是用VirtualAlloc分配內存,他不是在堆,也不是在棧是直接在進程的地址空間中保留一快內存,雖然用起來最不方便。但是速度, 也最靈活
2.5堆和棧中的存儲內容
棧: 在函數調用時,第一個進棧的是主函數中後的下一條指令(函數調用語句的下一條可執行語句)的地址,然後是函數的各個參數,在大多數的C編譯器中,參數是由右往左入棧的,然後是函數中的局部變數。注意靜態變數是不入棧的。
當本次函數調用結束後,局部變數先出棧,然後是參數,最後棧頂指針指向最開始存的地址,也就是主函數中的下一條指令,程序由該點繼續運行。
堆:一般是在堆的頭部用一個位元組存放堆的大小。堆中的具體內容有程序員安排。
2.6存取效率的比較
char s1[] = "aaaaaaaaaaaaaaa";
char *s2 = "bbbbbbbbbbbbbbbbb";
aaaaaaaaaaa是在運行時刻賦值的;
而bbbbbbbbbbb是在編譯時就確定的;
但是,在以後的存取中,在棧上的數組比指針所指向的字元串(例如堆)快。
比如:
#include
void main()
{
char a = 1;
char c[] = "1234567890";
char *p ="1234567890";
a = c[1];
a = p[1];
return;
}
對應的匯編代碼
10: a = c[1];
00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh]
0040106A 88 4D FC mov byte ptr [ebp-4],cl
11: a = p[1];
0040106D 8B 55 EC mov edx,dword ptr [ebp-14h]
00401070 8A 42 01 mov al,byte ptr [edx+1]
00401073 88 45 FC mov byte ptr [ebp-4],al
第一種在讀取時直接就把字元串中的元素讀到寄存器cl中,而第二種則要先把指edx中,在根據edx讀取字元,顯然慢了。
?
2.7小結:
堆和棧的區別可以用如下的比喻來看出:
使用棧就象我們去飯館里吃飯,只管點菜(發出申請)、付錢、和吃(使用),吃飽了就走,不必理會切菜、洗菜等准備工作和洗碗、刷鍋等掃尾工作,他的好處是快捷,但是自由度小。
使用堆就象是自己動手做喜歡吃的菜餚,比較麻煩,但是比較符合自己的口味,而且自由度大。
堆和棧的區別主要分:
操作系統方面的堆和棧,如上面說的那些,不多說了。
還有就是數據結構方面的堆和棧,這些都是不同的概念。這里的堆實際上指的就是(滿足堆性質的)優先隊列的一種數據結構,第1個元素有最高的優先權;棧實際上就是滿足先進後出的性質的數學或數據結構。
雖然堆棧,堆棧的說法是連起來叫,但是他們還是有很大區別的,連著叫只是由於歷史的原因針值讀
C. 只有大小固定的數據類型才能使用堆棧來保存
摘要 您好。每個進程和線程都有一個堆棧指針用來指示當前堆棧內存的使用情況,當堆棧內存中沒有內容時,堆棧指針指向堆棧的最高地址。只有大小固定的數據類型才能使用堆棧內存來保存。
D. 如何在進程崩潰後列印堆棧並防止數據丟失
進程在運行過程中遇到邏輯錯誤, 比如除零, 空指針等等, 系統會觸發一個軟體中斷.
這個中斷會以信號的方式通知進程, 這些信號的默認處理方式是結束進程.
發生這種情況, 我們就認為進程崩潰了.
進程崩潰後, 我們會希望知道它是為何崩潰的, 是哪個函數, 哪行代碼引起的錯誤.
另外, 在進程退出前, 我們還希望做一些善後處理, 比如把某些數據存入資料庫, 等等.
下面, 我會介紹一些技術來達成這兩個目標.
1. 在core文件中查看堆棧信息
如果進程崩潰時, 我們能看到當時的堆棧信息, 就能很快定位到錯誤的代碼.
在 gcc 中加入 -g 選項, 可執行文件中便會包含調試信息. 進程崩潰後, 會生成一個 core 文件.
我們可以用 gdb 查看這個 core 文件, 從而知道進程崩潰時的環境.
在調試階段, core文件能給我們帶來很多便利. 但是在正式環境中, 它有很大的局限:
1. 包含調試信息的可執行文件會很大. 並且運行速度也會大幅降低.
2. 一個 core 文件常常很大, 如果進程頻繁崩潰, 硬碟資源會變得很緊張.
所以, 在正式環境中運行的程序, 不會包含調試信息.
它的core文件的大小, 我們會把它設為0, 也就是不會輸入core文件.
在這個前提下, 我們如何得到進程的堆棧信息呢?
2. 動態獲取線程的堆棧
c 語言提供了 backtrace 函數, 通過這個函數可以動態的獲取當前線程的堆棧.
要使用 backtrace 函數, 有兩點要求:
1. 程序使用的是 ELF 二進制格式.
2. 程序連接時使用了 -rdynamic 選項.
-rdynamic可用來通知鏈接器將所有符號添加到動態符號表中, 這些信息比 -g 選項的信息要少得多.
下面是將要用到的函數說明:
#include <execinfo.h>
int backtrace(void **buffer,int size);
用於獲取當前線程的調用堆棧, 獲取的信息將會被存放在buffer中, 它是一個指針列表。
參數 size 用來指定buffer中可以保存多少個void* 元素。
函數返回值是實際獲取的指針個數, 最大不超過size大小
注意: 某些編譯器的優化選項對獲取正確的調用堆棧有干擾,
另外內聯函數沒有堆棧框架; 刪除框架指針也會導致無法正確解析堆棧內容;
char ** backtrace_symbols (void *const *buffer, int size)
把從backtrace函數獲取的信息轉化為一個字元串數組.
參數buffer應該是從backtrace函數獲取的指針數組,
size是該數組中的元素個數(backtrace的返回值) ;
函數返回值是一個指向字元串數組的指針, 它的大小同buffer相同.
每個字元串包含了一個相對於buffer中對應元素的可列印信息.
它包括函數名,函數的偏移地址, 和實際的返回地址.
該函數的返回值是通過malloc函數申請的空間, 因此調用者必須使用free函數來釋放指針.
注意: 如果不能為字元串獲取足夠的空間, 函數的返回值將會為NULL.
void backtrace_symbols_fd (void *const *buffer, int size, int fd)
與backtrace_symbols 函數具有相同的功能,
不同的是它不會給調用者返回字元串數組, 而是將結果寫入文件描述符為fd的文件中,每個函數對應一行.
3. 捕捉信號
我們希望在進程崩潰時列印堆棧, 所以我們需要捕捉到相應的信號. 方法很簡單.
#include <signal.h>
void (*signal(int signum,void(* handler)(int)))(int);
或者: typedef void(*sig_t) ( int );
sig_t signal(int signum,sig_t handler);
參數說明:
第一個參數signum指明了所要處理的信號類型,它可以是除了SIGKILL和SIGSTOP外的任何一種信號。
第二個參數handler描述了與信號關聯的動作,它可以取以下三種值:
1. 一個返回值為正數的函數的地址, 也就是我們的信號處理函數.
這個函數應有如下形式的定義: int func(int sig); sig是傳遞給它的唯一參數。
執行了signal()調用後,進程只要接收到類型為sig的信號,不管其正在執行程序的哪一部分,就立即執行func()函數。
當func()函數執行結束後,控制權返回進程被中斷的那一點繼續執行。
2. SIGIGN, 忽略該信號.
3. SIGDFL, 恢復系統對信號的默認處理。
返回值: 返回先前的信號處理函數指針,如果有錯誤則返回SIG_ERR(-1)。
注意:
當一個信號的信號處理函數執行時,如果進程又接收到了該信號,該信號會自動被儲存而不會中斷信號處理函數的執行,
直到信號處理函數執行完畢再重新調用相應的處理函數。
如果在信號處理函數執行時進程收到了其它類型的信號,該函數的執行就會被中斷。
在信號發生跳轉到自定的handler處理函數執行後,系統會自動將此處理函數換回原來系統預設的處理方式,
如果要改變此操作請改用sigaction()。
4. 實例
下面我們實際編碼, 看看具體如何在捕捉到信號後, 列印進程堆棧, 然後結束進程.
#include <iostream>
#include <time.h>
#include <signal.h>
#include <string.h>
#include <execinfo.h>
#include <fcntl.h>
#include <map>
using namespace std;
map<int, string> SIG_LIST;
#define SET_SIG(sig) SIG_LIST[sig] = #sig;
void SetSigList(){
SIG_LIST.clear();
SET_SIG(SIGILL)//非法指令
SET_SIG(SIGBUS)//匯流排錯誤
SET_SIG(SIGFPE)//浮點異常
SET_SIG(SIGABRT)//來自abort函數的終止信號
SET_SIG(SIGSEGV)//無效的存儲器引用(段錯誤)
SET_SIG(SIGPIPE)//向一個沒有讀用戶的管道做寫操作
SET_SIG(SIGTERM)//軟體終止信號
SET_SIG(SIGSTKFLT)//協處理器上的棧故障
SET_SIG(SIGXFSZ)//文件大小超出限制
SET_SIG(SIGTRAP)//跟蹤陷阱
}
string& GetSigName(int sig){
return SIG_LIST[sig];
}
void SaveBackTrace(int sig){
//打開文件
time_t tSetTime;
time(&tSetTime);
tm* ptm = localtime(&tSetTime);
char fname[256] = {0};
sprintf(fname, "core.%d-%d-%d_%d_%d_%d",
ptm->tm_year+1900, ptm->tm_mon+1, ptm->tm_mday,
ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
FILE* f = fopen(fname, "a");
if (f == NULL){
exit(1);
}
int fd = fileno(f);
//鎖定文件
flock fl;
fl.l_type = F_WRLCK;
fl.l_start = 0;
fl.l_whence = SEEK_SET;
fl.l_len = 0;
fl.l_pid = getpid();
fcntl(fd, F_SETLKW, &fl);
//輸出程序的絕對路徑
char buffer[4096];
memset(buffer, 0, sizeof(buffer));
int count = readlink("/proc/self/exe", buffer, sizeof(buffer));
if(count > 0){
buffer[count] = '\n';
buffer[count + 1] = 0;
fwrite(buffer, 1, count+1, f);
}
//輸出信息的時間
memset(buffer, 0, sizeof(buffer));
sprintf(buffer, "Dump Time: %d-%d-%d %d:%d:%d\n",
ptm->tm_year+1900, ptm->tm_mon+1, ptm->tm_mday,
ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
fwrite(buffer, 1, strlen(buffer), f);
//線程和信號
sprintf(buffer, "Curr thread: %d, Catch signal:%s\n",
pthread_self(), GetSigName(sig).c_str());
fwrite(buffer, 1, strlen(buffer), f);
//堆棧
void* DumpArray[256];
int nSize = backtrace(DumpArray, 256);
sprintf(buffer, "backtrace rank = %d\n", nSize);
fwrite(buffer, 1, strlen(buffer), f);
if (nSize > 0){
char** symbols = backtrace_symbols(DumpArray, nSize);
if (symbols != NULL){
for (int i=0; i<nSize; i++){
fwrite(symbols[i], 1, strlen(symbols[i]), f);
fwrite("\n", 1, 1, f);
}
free(symbols);
}
}
//文件解鎖後關閉, 最後終止進程
fl.l_type = F_UNLCK;
fcntl(fd, F_SETLK, &fl);
fclose(f);
exit(1);
}
void SetSigCatchFun(){
map<int, string>::iterator it;
for (it=SIG_LIST.begin(); it!=SIG_LIST.end(); it++){
signal(it->first, SaveBackTrace);
}
}
void Fun(){
int a = 0;
int b = 1 / a;
}
static void* ThreadFun(void* arg){
Fun();
return NULL;
}
int main(){
SetSigList();
SetSigCatchFun();
printf("main thread id = %d\n", (pthread_t)pthread_self());
pthread_t pid;
if (pthread_create(&pid, NULL, ThreadFun, NULL)){
exit(1);
}
printf("fun thread id = %d\n", pid);
for(;;){
sleep(1);
}
return 0;
}
文件名為 bt.cpp
編譯: g++ bt.cpp -rdynamic -I /usr/local/include -L /usr/local/lib -pthread -o bt
主線程創建了 fun 線程, fun 線程有一個除零錯誤, 系統拋出 SIGFPE 信號.
該信號使 fun 線程中斷, 我們注冊的 SaveBackTrace 函數捕獲到這個信號, 列印相關信息, 然後終止進程.
在輸出的core文件中, 我們可以看到簡單的堆棧信息.
5. 善後處理
在上面的例子中, fun 線程被 SIGFPE 中斷, 轉而執行 SaveBackTrace 函數.
此時, main 線程仍然在正常運行.
如果我們把 SaveBackTrace 函數最後的 exit(1); 替換成 for(;;)sleep(1);
main 線程就可以一直正常的運行下去.
利用這個特點, 我們可以做很多其它事情.
游戲的伺服器進程常常有這些線程:
網路線程, 資料庫線程, 業務處理線程. 引發邏輯錯誤的代碼常常位於業務處理線程.
而資料庫線程由於功能穩定, 邏輯簡單, 是十分強壯的.
那麼, 如果業務處理線程有邏輯錯誤, 我們捕捉到信號後, 可以在信號處理函數的最後,
通知資料庫線程保存游戲數據.
直到資料庫線程把游戲信息全部存入資料庫, 信號處理函數才返回.
這樣, 伺服器宕機不會導致回檔, 損失被大大降低.
要實現這個機制, 要求資料庫模塊和業務處理模塊具有低耦合度.
當然, 實際應用的時候, 還有許多細節要考慮.
比如, 業務處理線程正在處理玩家的數據, 由於發生不可預知的錯誤, 玩家的數據被損壞了, 這些玩家的數據就不應該被存入資料庫.
E. 怎樣將mp文件中的堆棧信息存儲為文本格式
列印堆棧是調試的常用方法,一般在系統異常時,我們可以將異常情況下的堆棧列印出來,這樣十分方便錯誤查找。實際上還有另外一個非常有用的功能:分析代碼的行為。android代碼太過龐大復雜了,完全的靜態分析經常是無從下手,因此通過列印堆棧的動態分析也十分必要。
Android列印堆棧的方法,簡單歸類一下
1. zygote的堆棧mp
實際上這個可以同時mp java線程及native線程的堆棧,對於java線程,java堆棧和native堆棧都可以得到。
使用方法很簡單,直接在adb shell或串口中輸入:
[plain] view plain
kill -3 <pid>
輸出的trace會保存在 /data/anr/traces.txt文件中。這個需要注意,如果沒有 /data/anr/這個目錄或/data/anr/traces.txt這個文件,需要手工創建一下,並設置好讀寫許可權。
如果需要在代碼中,更容易控制堆棧的輸出時機,可以用以下命令獲取zygote的core mp:
[java] view plain
Process.sendSignal(pid, Process.SIGNAL_QUIT);
原理和命令行是一樣的。
不過需要注意兩點:
adb shell可能會沒有許可權,需要root。
android 4.2中關閉了native thread的堆棧列印,詳見 dalvik/vm/Thread.cpp的mpNativeThread方法:
[cpp] view plain
dvmPrintDebugMessage(target,
"\"%s\" sysTid=%d nice=%d sched=%d/%d cgrp=%s\n",
name, tid, getpriority(PRIO_PROCESS, tid),
schedStats.policy, schedStats.priority, schedStats.group);
mpSchedStat(target, tid);
// Temporarily disabled collecting native stacks from non-Dalvik
// threads because sometimes they misbehave.
//dvmDumpNativeStack(target, tid);
Native堆棧的列印被關掉了!不過對於大多數情況,可以直接將這個注釋打開。
2. debuggerd的堆棧mp
debuggerd是android的一個daemon進程,負責在進程異常出錯時,將進程的運行時信息mp出來供分析。debuggerd生 成的coremp數據是以文本形式呈現,被保存在 /data/tombstone/ 目錄下(名字取的也很形象,tombstone是墓碑的意思),共可保存10個文件,當超過10個時,會覆蓋重寫最早生成的文件。從4.2版本開 始,debuggerd同時也是一個實用工具:可以在不中斷進程執行的情況下列印當前進程的native堆棧。使用方法是:
[plain] view plain
debuggerd -b <pid>
這可以協助我們分析進程執行行為,但最最有用的地方是:它可以非常簡單的定位到native進程中鎖死或錯誤邏輯引起的死循環的代碼位置。
3. java代碼中列印堆棧
Java代碼列印堆棧比較簡單, 堆棧信息獲取和輸出,都可以通過Throwable類的方法實現。目前通用的做法是在java進程出現需要注意的異常時,列印堆棧,然後再決定退出或挽救。通常的方法是使用exception的printStackTrace()方法:
[java] view plain
try {
...
} catch (RemoteException e) {
e.printStackTrace();
...
}
當然也可以只列印堆棧不退出,這樣就比較方便分析代碼的動態運行情況。Java代碼中插入堆棧列印的方法如下:
[java] view plain
Log.d(TAG,Log.getStackTraceString(new Throwable()));
4. C++代碼中列印堆棧
C++也是支持異常處理的,異常處理庫中,已經包含了獲取backtrace的介面,Android也是利用這個介面來列印堆棧信息的。在Android的C++中,已經集成了一個工具類CallStack,在libutils.so中。使用方法:
[cpp] view plain
#include <utils/CallStack.h>
...
CallStack stack;
stack.update();
stack.mp();
使用方式比較簡單。目前Andoid4.2版本已經將相關信息解析的很到位,符號表查找,demangle,偏移位置校正都做好了。
[plain] view plain
5. C代碼中列印堆棧
C代碼,尤其是底層C庫,想要看到調用的堆棧信息,還是比較麻煩的。 CallStack肯定是不能用,一是因為其實C++寫的,需要重新封裝才能在C中使用,二是底層庫反調上層庫的函數,會造成鏈接器循環依賴而無法鏈接。 不過也不是沒有辦法,可以通過android工具類CallStack實現中使用的unwind調用及符號解析函數來處理。
這里需要注意的是,為解決鏈接問題,最好使用dlopen方式,查找需要用到的介面再直接調用,這樣會比較簡單。如下為相關的實現代碼,只需要在要 列印的文件中插入此部分代碼,然後調用getCallStack()即可,無需包含太多的頭文件和修改Android.mk文件:
[cpp] view plain
#define MAX_DEPTH 31
#define MAX_BACKTRACE_LINE_LENGTH 800
#define PATH "/system/lib/libcorkscrew.so"
typedef ssize_t (*unwindFn)(backtrace_frame_t*, size_t, size_t);
typedef void (*unwindSymbFn)(const backtrace_frame_t*, size_t, backtrace_symbol_t*);
typedef void (*unwindSymbFreeFn)(backtrace_symbol_t*, size_t);
static void *gHandle = NULL;
static int getCallStack(void){
ssize_t i = 0;
ssize_t result = 0;
ssize_t count;
backtrace_frame_t mStack[MAX_DEPTH];
backtrace_symbol_t symbols[MAX_DEPTH];
unwindFn unwind_backtrace = NULL;
unwindSymbFn get_backtrace_symbols = NULL;
unwindSymbFreeFn free_backtrace_symbols = NULL;
// open the so.
if(gHandle == NULL) gHandle = dlopen(PATH, RTLD_NOW);
// get the interface for unwind and symbol analyse
if(gHandle != NULL) unwind_backtrace = (unwindFn)dlsym(gHandle, "unwind_backtrace");
if(gHandle != NULL) get_backtrace_symbols = (unwindSymbFn)dlsym(gHandle, "get_backtrace_symbols");
if(gHandle != NULL) free_backtrace_symbols = (unwindSymbFreeFn)dlsym(gHandle, "free_backtrace_symbols");
if(!gHandle ||!unwind_backtrace ||!get_backtrace_symbols || !free_backtrace_symbols ){
ALOGE("Error! cannot get unwind info: handle:%p %p %p %p",
gHandle, unwind_backtrace, get_backtrace_symbols, free_backtrace_symbols );
return result;
}
count= unwind_backtrace(mStack, 1, MAX_DEPTH);
get_backtrace_symbols(mStack, count, symbols);
for (i = 0; i < count; i++) {
char line[MAX_BACKTRACE_LINE_LENGTH];
const char* mapName = symbols[i].map_name ? symbols[i].map_name : "<unknown>";
const char* symbolName =symbols[i].demangled_name ? symbols[i].demangled_name : symbols[i].symbol_name;
size_t fieldWidth = (MAX_BACKTRACE_LINE_LENGTH - 80) / 2;
if (symbolName) {
uint32_t pc_offset = symbols[i].relative_pc - symbols[i].relative_symbol_addr;
if (pc_offset) {
snprintf(line, MAX_BACKTRACE_LINE_LENGTH, "#%02d pc %08x %.*s (%.*s+%u)",
i, symbols[i].relative_pc, fieldWidth, mapName,
fieldWidth, symbolName, pc_offset);
} else {
snprintf(line, MAX_BACKTRACE_LINE_LENGTH, "#%02d pc %08x %.*s (%.*s)",
i, symbols[i].relative_pc, fieldWidth, mapName,
fieldWidth, symbolName);
}
} else {
snprintf(line, MAX_BACKTRACE_LINE_LENGTH, "#%02d pc %08x %.*s",
i, symbols[i].relative_pc, fieldWidth, mapName);
}
ALOGD("%s", line);
}
free_backtrace_symbols(symbols, count);
return result;
}
對sched_policy.c的堆棧調用分析如下,注意具體是否要列印,在哪裡列印,還可以通過pid、uid、property等來控制一下,這樣就不會被淹死在trace的汪洋大海中。
[plain] view plain
D/SchedPolicy( 1350): #00 pc 0000676c /system/lib/libcutils.so
D/SchedPolicy( 1350): #01 pc 00006b3a /system/lib/libcutils.so (set_sched_policy+49)
D/SchedPolicy( 1350): #02 pc 00010e82 /system/lib/libutils.so (androidSetThreadPriority+61)
D/SchedPolicy( 1350): #03 pc 00068104 /system/lib/libandroid_runtime.so (android_os_Process_setThreadPriority(_JNIEnv*, _jobject*, int, int)+7)
D/SchedPolicy( 1350): #04 pc 0001e510 /system/lib/libdvm.so (dvmPlatformInvoke+112)
D/SchedPolicy( 1350): #05 pc 0004d6aa /system/lib/libdvm.so (dvmCallJNIMethod(unsigned int const*, JValue*, Method const*, Thread*)+417)
D/SchedPolicy( 1350): #06 pc 00027920 /system/lib/libdvm.so
D/SchedPolicy( 1350): #07 pc 0002b7fc /system/lib/libdvm.so (dvmInterpret(Thread*, Method const*, JValue*)+184)
D/SchedPolicy( 1350): #08 pc 00060c30 /system/lib/libdvm.so (dvmCallMethodV(Thread*, Method const*, Object*, bool, JValue*, std::__va_list)+271)
D/SchedPolicy( 1350): #09 pc 0004cd34 /system/lib/libdvm.so
D/SchedPolicy( 1350): #10 pc 00049382 /system/lib/libandroid_runtime.so
D/SchedPolicy( 1350): #11 pc 00065e52 /system/lib/libandroid_runtime.so
D/SchedPolicy( 1350): #12 pc 0001435e /system/lib/libbinder.so (android::BBinder::transact(unsigned int, android::Parcel const&, android::Parcel*, unsigned int)+57)
D/SchedPolicy( 1350): #13 pc 00016f5a /system/lib/libbinder.so (android::IPCThreadState::executeCommand(int)+513)
D/SchedPolicy( 1350): #14 pc 00017380 /system/lib/libbinder.so (android::IPCThreadState::joinThreadPool(bool)+183)
D/SchedPolicy( 1350): #15 pc 0001b160 /system/lib/libbinder.so
D/SchedPolicy( 1350): #16 pc 00011264 /system/lib/libutils.so (android::Thread::_threadLoop(void*)+111)
D/SchedPolicy( 1350): #17 pc 000469bc /system/lib/libandroid_runtime.so (android::AndroidRuntime::javaThreadShell(void*)+63)
D/SchedPolicy( 1350): #18 pc 00010dca /system/lib/libutils.so
D/SchedPolicy( 1350): #19 pc 0000e3d8 /system/lib/libc.so (__thread_entry+72)
D/SchedPolicy( 1350): #20 pc 0000dac4 /system/lib/libc.so (pthread_create+160)
D/SchedPolicy( 1350): #00 pc 0000676c /system/lib/libcutils.so
D/SchedPolicy( 1350): #01 pc 00006b3a /system/lib/libcutils.so (set_sched_policy+49)
D/SchedPolicy( 1350): #02 pc 00016f26 /system/lib/libbinder.so (android::IPCThreadState::executeCommand(int)+461)
D/SchedPolicy( 1350): #03 pc 00017380 /system/lib/libbinder.so (android::IPCThreadState::joinThreadPool(bool)+183)
D/SchedPolicy( 1350): #04 pc 0001b160 /system/lib/libbinder.so
D/SchedPolicy( 1350): #05 pc 00011264 /system/lib/libutils.so (android::Thread::_threadLoop(void*)+111)
D/SchedPolicy( 1350): #06 pc 000469bc /system/lib/libandroid_runtime.so (android::AndroidRuntime::javaThreadShell(void*)+63)
D/SchedPolicy( 1350): #07 pc 00010dca /system/lib/libutils.so
D/SchedPolicy( 1350): #08 pc 0000e3d8 /system/lib/libc.so (__thread_entry+72)
D/SchedPolicy( 1350): #09 pc 0000dac4 /system/lib/libc.so (pthread_create+160)
6. 其它堆棧信息查詢
F. 堆棧一般用來存放什麼內容
SP是堆棧指示器
BP是基址指示器
SP,BP一般與段寄存器SS
聯用,以確定堆棧寄存器中某一單元的地址,SP用以指示棧頂的偏移地址,而BP可
作為堆棧區中的一個基地址,所以通常堆棧的數據區基地址是放在BP中
G. 堆棧的功能,操作過程和特點
堆棧其實是數據結果中的兩個概念 ,是存放數據的方式,堆:順序隨意;棧:後進先出(Last-In/First-Out)。要說用處,那就是在寫代碼的時候,有時數據存取肯定是要有規定的順序的,這個是你自己規定的,然後按照你所寫程序的用處的特點來用堆還是棧還是隊列之類的順序 追問: 程序設計時,為什麼要對堆棧指針SP重新賦值? 回答: 這不是初始化嘛
堆棧是個特殊的存儲區,主要功能是暫時存放數據和地址,通常用來保護斷點和現場。它的特點是按照先進後出的原則存取數據,這里的進與出是指進棧與出棧操作。
80C51片內RAM的部分單元可以用做堆棧。有一個8位的堆棧指針寄存器SP,專用於指出當前堆棧頂部是片內RAM的哪一個單元。80C51單片機系統復位後SP的初值為07H,也就是將從內部RAM的08H單元開始堆放信息。
但是,80C51系列的棧區不是固定的,只要通過軟體改變SP寄存器的值便可更動棧區。為了避開工作寄存器區和位定址區,SP的初值可置為2FH或更大的地址值。如果CPU在操作中要使用兩組工作寄存器,如果不使用位變數,SP的初值至少應為0FH或更大的值;如果使用位變數,SP的初值至少應為2FH或更大的值;KeilC51編譯器會自動計算SP的初始設定值,無需編程者關心。
H. java中的「堆棧」是什麼意思
堆棧是計算機為程序分配的內存空間,用來存儲數據的。
I. 進程切換時所要保存的處理機狀態信息有哪些
進程切換時所要保存的處理機狀態信息有進程當前暫存信息。
進行進程切換就是從正在運行的進程中收回處理器,然後再使待運行進程來佔用處理器。 這里所說的從某個進程收回處理器。
實質上就是把進程存放在處理器的寄存器中的中間數據找個地方存起來,從而把處理器的寄存器騰出來讓其他進程使用。
(9)進程堆棧存儲信息擴展閱讀:
在切換時,一個進程存儲在處理器各寄存器中的中間數據叫做進程的上下文,所以進程的切換實質上就是被中止運行進程與待運行進程上下文的切換。在進程未佔用處理器時,進程的上下文是存儲在進程的私有堆棧中的。
當前執行進程,實際上是指調用上下文切換程序之前的執行進程。如果上下文切換不是被那個當前執行進程所調用,且不屬於該進程,則所保存的上下文應是先前執行進程的上下文,或稱為「老」進程上下文。
J. 在操作系統中,一個進程由哪些部分組成
一個計算機系統進程包括(或者說「擁有」)下列數據:
那個程序的可運行機器碼的一個在存儲器的映像。 分配到的存儲器(通常包括虛擬內存的一個區域)。存儲器的內容包括可運行代碼、特定於進程的數據(輸入、輸出)、調用堆棧、堆棧(用於保存運行時運數中途產生的數據)。
分配給該進程的資源的操作系統描述符,諸如文件描述符(Unix術語)或文件句柄(Windows)、數據源和數據終端。 安全特性,諸如進程擁有者和進程的許可權集(可以容許的操作)。 處理器狀態(內文),諸如寄存器內容、物理存儲器定址等。當進程正在運行時,狀態通常儲存在寄存器,其他情況在存儲器。
(10)進程堆棧存儲信息擴展閱讀
如果系統發生了上述要求終止進程的某事件後,OS便調用進程終止原語,按下述過程去終止指定的進程。
1)根據被終止進程的標識符,從PCB集合中檢索出該進程的PCB,從中讀出該進程狀態。
2)若被終止進程正處於執行狀態,應立即終止該進程的執行,並置調度標志為真。用於指示該進程被終止後應重新進行調度。
3)若該進程還有子孫進程,還應將其所有子孫進程予以終止,以防他們成為不可控的進程。
4)將被終止的進程所擁有的全部資源,或者歸還給其父進程,或者歸還給系統。
5)將被終止進程(它的PCB)從所在隊列(或鏈表)中移出,等待其它程序來搜集信息。