1. linux下多線程程序崩潰時怎麼提取出所有線程的函數調用棧
gcc編譯時加-g參數,然後用gdb去跑,掛掉的時候使用bt命令就可以看到某一線程的調用棧了,你可以使用thread命令去切換線程,就可以看到不同線程的調用棧了,具體去網路一下gdb的用法就行了。
另:
還可以把堆棧錯誤給mp core,如果你覺得有必要的話。
2. linux下main函數的參數是在棧里還是在堆里
不是的. 每一個函數就是一個堆棧. 每調用一次, 都會有指針指向它. 而變數是加在這些堆棧中的(C語言一般是在堆棧的棧頂, C++都是哪用到在哪)
學高級語言的這些一般是不用在意的, 只有學匯編才用管它.
3. linux 網路路徑中網路協議棧有幾種
1.總述
Linux中用戶空間的網路編程,是以socket為介面,一般創建一個sockfd = socket(family,type,protocol),之後以該sockfd為參數,進行各種系統調用來實現網路通信功能。其中family指明使用哪種協議域(如INET、UNIX等),protocol指明該協議域中具體哪種協議(如INET中的TCP、UDP等),type表明該介面的類型(如STREAM、DGRAM等),一般設protocol=0,那麼就會用該family中該type類型的默認協議(如INET中的STREAM默認就是TCP協議)。
Linux中利用mole機制,層次分明地實現了這套協議體系,並具有很好的擴展性,其基本模塊構成如下:
先看右邊,頂層的socket模塊提供一個sock_register()函數,供各個協議域模塊使用,在全局的net_family[]數組中增加一項;各個協議域模塊也提供一個類似的register_xx_proto()函數,供各個具體的協議使用,在該協議域私有的xx_proto[]數組中增加一項。這兩個數組中的存放的都是指針,指向的數據結構如下圖所示:
很明顯它們是用來創建不同類型的socket介面的,且是一種分層次的創建過程,可想而知,頂層socket_create()完成一些共有的操作,如分配內存等,然後調用下一層create;協議域內的create()完成一些該協議域內共有的初始化工作;最後具體協議中的create()完成協議特有的初始化。具體的下一節講。
再來看上圖右邊的,也是頂層socket模塊提供的4個函數,前兩個一般由具體協議模塊調用,由於協議棧與應用層的交互,具體的後面會講到。後兩個一般有協議域模塊調用,用於底層設備與協議棧間的交互。但這也不絕對,如在PPPOE協議中,這4個函數都由具體協議模塊調用,這是因為PPPOX協議域內的共有部分不多,各個協議間幾乎獨立。這4個函數的功能及所用到的數據結構,在後面具體用到時會詳細說明。
2.socket插口創建
首先來看一下最終創建好的socket插口由哪些部分組成,該結構是相當龐大的,這里只給出框架:
基本屬性有state(listen、accept等),flags標志(blocked等),type類型,這里family和protocol都沒有了,因為它們再創建時使用過了,已經被融入到socket結構中。
File指針指向一個file結構,在Linux中一個socket也被抽象為一個文件,所以在應用層一般通過標準的文件操作來操作它。
Ops指向一個struct proto_ops結構,它是每種協議特有的,應用層的系統調用,最終映射到網路棧中具體協議的操作方法。
Sk指向一個struct sock結構,而該結構在分配空間時,多分配了一點以作為該協議的私有部分,這里包含了該協議的具體信息,內容相當多。首先是一個struct sock_common結構,包含了協議的基本信息;然後是一個sk_prot_create指針,指向一個struct proto結構體,該結構體就是第一節中所述的,用proto_regsiter()注冊到內核中的,它包含應用層到協議棧的交互操作和信息(也可以說成是Appà transport layer的交互信息);然後還有一個sk_backlog_rcv函數指針,所指函數在協議棧處理完接收到的包之後調用,一般僅是把數據包放到該socket的接收隊列中,等待APP讀取;最後協議的私有部分里存放該協議的私有信息,如pppoe的sessionID、daddr,tcp的連接4元組等,這些信息很重要,利用它們來區分同一個協議中的多個socket。
附上出處鏈接:http://blog.csdn.net/vfatfish/article/details/9296885
4. linux堆棧地址錯誤與報錯函數偏移怎麼算
一般察看函數運行時堆棧的方法是使用GDB(bt命令)之類的外部調試器,但是,有些時候為了分析程序的BUG,(主要針對長時間運行程序的分析),在程序出錯時列印出函數的調用堆棧是非常有用的。
在glibc頭文件"execinfo.h"中聲明了三個函數用於獲取當前線程的函數調用堆棧。
[cpp] view plain print?
int backtrace(void **buffer,int size)
該函數用於獲取當前線程的調用堆棧,獲取的信息將會被存放在buffer中,它是一個指針列表。參數 size 用來指定buffer中可以保存多少個void* 元素。函數返回值是實際獲取的指針個數,最大不超過size大小
在buffer中的指針實際是從堆棧中獲取的返回地址,每一個堆棧框架有一個返回地址
注意:某些編譯器的優化選項對獲取正確的調用堆棧有干擾,另外內聯函數沒有堆棧框架;刪除框架指針也會導致無法正確解析堆棧內容
[cpp] view plain print?
char ** backtrace_symbols (void *const *buffer, int size)
backtrace_symbols將從backtrace函數獲取的信息轉化為一個字元串數組. 參數buffer應該是從backtrace函數獲取的指針數組,size是該數組中的元素個數(backtrace的返回值)
函數返回值是一個指向字元串數組的指針,它的大小同buffer相同.每個字元串包含了一個相對於buffer中對應元素的可列印信息.它包括函數名,函數的偏移地址,和實際的返回地址
現在,只有使用ELF二進制格式的程序才能獲取函數名稱和偏移地址.在其他系統,只有16進制的返回地址能被獲取.另外,你可能需要傳遞相應的符號給鏈接器,以能支持函數名功能(比如,在使用GNU ld鏈接器的系統中,你需要傳遞(-rdynamic), -rdynamic可用來通知鏈接器將所有符號添加到動態符號表中,如果你的鏈接器支持-rdynamic的話,建議將其加上!)
該函數的返回值是通過malloc函數申請的空間,因此調用者必須使用free函數來釋放指針.
注意:如果不能為字元串獲取足夠的空間函數的返回值將會為NULL
[cpp] view plain print?
void backtrace_symbols_fd (void *const *buffer, int size, int fd)
backtrace_symbols_fd與backtrace_symbols 函數具有相同的功能,不同的是它不會給調用者返回字元串數組,而是將結果寫入文件描述符為fd的文件中,每個函數對應一行.它不需要調用malloc函數,因此適用於有可能調用該函數會失敗的情況
下面是glibc中的實例(稍有修改):
[cpp] view plain print?
#include <execinfo.h>
#include <stdio.h>
#include <stdlib.h>
/* Obtain a backtrace and print it to @code{stdout}. */
void print_trace (void)
{
void *array[10];
size_t size;
char **strings;
size_t i;
size = backtrace (array, 10);
strings = backtrace_symbols (array, size);
if (NULL == strings)
{
perror("backtrace_synbols");
Exit(EXIT_FAILURE);
}
printf ("Obtained %zd stack frames.\n", size);
for (i = 0; i < size; i++)
printf ("%s\n", strings[i]);
free (strings);
strings = NULL;
}
/* A mmy function to make the backtrace more interesting. */
void mmy_function (void)
{
print_trace ();
}
int main (int argc, char *argv[])
{
mmy_function ();
return 0;
}
輸出如下:
[cpp] view plain print?
Obtained 4 stack frames.
./execinfo() [0x80484dd]
./execinfo() [0x8048549]
./execinfo() [0x8048556]
/lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf3) [0x70a113]
我們還可以利用這backtrace來定位段錯誤位置。
通常情況系,程序發生段錯誤時系統會發送SIGSEGV信號給程序,預設處理是退出函數。我們可以使用 signal(SIGSEGV, &your_function);函數來接管SIGSEGV信號的處理,程序在發生段錯誤後,自動調用我們准備好的函數,從而在那個函數里來獲取當前函數調用棧。
舉例如下:
[cpp] view plain print?
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <execinfo.h>
#include <signal.h>
void mp(int signo)
{
void *buffer[30] = {0};
size_t size;
char **strings = NULL;
size_t i = 0;
size = backtrace(buffer, 30);
fprintf(stdout, "Obtained %zd stack frames.nm\n", size);
strings = backtrace_symbols(buffer, size);
if (strings == NULL)
{
perror("backtrace_symbols.");
exit(EXIT_FAILURE);
}
for (i = 0; i < size; i++)
{
fprintf(stdout, "%s\n", strings[i]);
}
free(strings);
strings = NULL;
exit(0);
}
void func_c()
{
*((volatile char *)0x0) = 0x9999;
}
void func_b()
{
func_c();
}
void func_a()
{
func_b();
}
int main(int argc, const char *argv[])
{
if (signal(SIGSEGV, mp) == SIG_ERR)
perror("can't catch SIGSEGV");
func_a();
return 0;
}
編譯程序:
gcc -g -rdynamic test.c -o test; ./test
輸出如下:
[cpp] view plain print?
Obtained6stackframes.nm
./backstrace_debug(mp+0x45)[0x80487c9]
[0x468400]
./backstrace_debug(func_b+0x8)[0x804888c]
./backstrace_debug(func_a+0x8)[0x8048896]
./backstrace_debug(main+0x33)[0x80488cb]
/lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf3)[0x129113]
(這里有個疑問: 多次運行的結果是/lib/i368-Linux-gnu/libc.so.6和[0x468400]的返回地址是變化的,但不變的是後三位, 不知道為什麼)
接著:
objmp -d test > test.s
在test.s中搜索804888c如下:
[cpp] view plain print?
8048884 <func_b>:
8048884: 55 push %ebp
8048885: 89 e5 mov %esp, %ebp
8048887: e8 eb ff ff ff call 8048877 <func_c>
804888c: 5d pop %ebp
804888d: c3 ret
其中80488c時調用(call 8048877)C函數後的地址,雖然並沒有直接定位到C函數,通過匯編代碼, 基本可以推出是C函數出問題了(pop指令不會導致段錯誤的)。
我們也可以通過addr2line來查看
[cpp] view plain print?
addr2line 0x804888c -e backstrace_debug -f
輸出:
[cpp] view plain print?
func_b
/home/astrol/c/backstrace_debug.c:57
以下是簡單的backtrace原理實現:
5. 匯編、linux、嵌入式:考驗你們的時候到了,用自己的語言告訴我為什麼要設置棧
棧是一塊內存空間,用於保存數據,一般用於函數調用保存程序運行的現場和參數。舉例來說:
函數調用:fun(a,b,c);
系統會將當前程序的執行點的位置進行壓棧處理,即保存當前程序指針(程序運行位置),按照參數的順序將a,b,c的數值也壓入堆棧,以便傳送給被調用的函數然後跳轉到調用的函數入口;
調用函數將自己將要用到的寄存器和一些系統指針壓入棧保存,然後從棧取得這些參數進行運算,退出時將保存的寄存器和系統指針從棧中彈出,然後舍棄棧中的參數,利用棧中保存的程序運行位置指針返回調用點
返回調用點後,由於函數的上述壓棧保護和恢復現場工作,調用函數的運行環境一點也沒有被函數運行破壞,繼續向下面運行。
在c語言中,編譯系統自動將上述堆棧操作的語句替你添加上,而匯編程序設計時,這些堆棧操作就要程序設計者來完成了,如果有遺漏或棧操作順序不對將引起相關運行錯誤。
6. 在linux中,函數的返回值是如何傳遞的
去查一下「堆棧」這個詞,操作系統課里會講到「棧」的特點是先進後出(First In,Last Out).這種數據結構的硬體實現就是內存中棧的設置。棧的一個主要功能就是在函數調用過程中傳遞參數,每一次棧操作只有兩種可能:一是Push,即為壓棧,將數據或指令存入棧中;二是Pop,即為出棧,就是把數據從棧中取出來。
7. linux c函數返回值是在棧中還是寄存器
函數的返回值是在寄存器中,但僅限於返回的是值。
如果返回的地址,並且這個地址是個局部變數的地址,那麼就是在棧上,所以我們不建議返回這樣的地址結果。
如果返回的是一個malloc或者new的變數的地址,就是在堆上。如果要返回地址,建議這樣做,還要注意使用完成後進行內存釋放
8. linux為什麼需要內核棧,系統調用時直接使用用戶棧不行嗎
在空氣中噴出
9. linux中函數調用必須藉助堆棧嗎
現在的計算機體系設計就是這樣的,不單單是linux,也不單單是linux api 所有程序語言都一樣,解釋型語言,是由解釋器在做此事!除非哪天把計算機的體系結構給修改了。個人建議你不糾結這問題
10. linux程序設計:堆和棧的區別
一、預備知識—程序的內存分配
一個由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/0在常量區,p3在棧上。
static int c =0; 全局(靜態)初始化區
p1 = (char *)malloc(10);
p2 = (char *)malloc(20);
分配得來得10和20位元組的區域就在堆區。
strcpy(p1, "123456"); 123456/0放在常量區,編譯器可能會將它與p3所指向的"123456"
優化成一個地方。
}
二、堆和棧的理論知識
2.1申請方式
stack:
由系統自動分配。 例如,聲明在函數中一個局部變數 int b; 系統自動在棧中為b開辟空
間
heap:
需要程序員自己申請,並指明大小,在c中malloc函數
如p1 = (char *)malloc(10);
在C++中用new運算符
如p2 = new char[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小結:
堆和棧的區別可以用如下的比喻來看出:
使用棧就象我們去飯館里吃飯,只管點菜(發出申請)、付錢、和吃(使用),吃飽了就 走,不必理會切菜、洗菜等准備工作和洗碗、刷鍋等掃尾工作,他的好處是快捷,但是自 由度小。
使用堆就象是自己動手做喜歡吃的菜餚,比較麻煩,但是比較符合自己的口味,而且自由