成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

程序員必備高級技術之函數調用棧

系統 Linux
本文將介紹一下在Linux平臺下函數棧是如何實現的。有些同學可能覺得沒必要了解這么深入,其實非也。根據本號多年的經驗,了解系統深層次的原理對分析疑難問題有很好的幫助。

大家都知道函數調用是通過棧來實現的,而且知道在棧中存放著該函數的局部變量。但是對于棧的實現細節可能不一定清楚。

圖0 函數棧

就像熟悉抓包是解決網絡通信問題的高級武器一樣,熟悉函數調用棧則是分析程序內存問題的高級武器。本文以Linux 64位操作系統下C語言開發為例,介紹應用程序調用棧的實現原理,并通過一個實例和GDB工具具體分析一下某個程序的調用棧內容。在介紹具體的調用棧之前,我們先介紹一些基礎知識,這些知識是理解后續函數調用棧的基礎。

X86 CPU的寄存器

CPU的寄存器是需要了解的基礎知識,這是因為在X64體系中函數的參數是通過寄存器傳遞的。如圖1是X86 CPU寄存器的列表及功能簡要說明。

圖1 Intel X86 CPU寄存器用途

我們知道Intel的CPU在設計的時候都是向前兼容的,也就是在新一代的CPU上可以運行老一代CPU上的編譯的程序。為了保證兼容性,新一代CPU保留了老一代寄存器的別名。以16位寄存器AX為例,AL表示低8位,AH表示高8位。而32位CPU問世之后,通過名為EAX的寄存器表示32位寄存器,AX仍然保留。以此類推,RAX表示一個64位寄存器。

圖2 不同的寄存器名稱

應用程序的地址空間

操作系統通過虛擬內存的方式為所有應用程序提供了統一的內存映射地址。如圖3所示,從上到下分別是用戶棧、共享庫內存、運行時堆和代碼段。當然這個是一個大概的分段,實際分段比這個可能稍微復雜一些,但整個格局沒有大變化。

圖3 應用程序的地址空間

從圖中可以看出用戶棧是從上往下生長的。也就是用戶棧會先占用高地址的空間,然后占用低地址空間。目前我們可以大體上有個了解即可,后面我們在詳細分析用戶棧的細節。

函數調用及匯編指令

為了理解函數調用棧的細節,有必要了解一下匯編程序中函數調用的實現。函數的調用主要分為2部分,一個是調用,另外一個是返回。在匯編語言中函數調用是通過call指令完成的,返回則是通過ret指令。

匯編語言的call指令相當于執行了2步操作,分別是,1)將當前的IP或CS和IP壓入棧中;2)跳轉,類似與jmp指令。同樣,ret指令也分2步,分別是,1)將棧中的地址彈出到IP寄存器;2)跳轉執行后續指令。這個基本上就是函數調用的原理。

除了在代碼間的跳動外,函數的調用往往還需要傳遞一個參數,而處理完成后還可能有返回值。這些數據的傳遞都是通過寄存器進行的。在函數調用之前通過上文介紹的寄存器存儲參數,函數返回之前通過RAX寄存器(32位系統為EAX)存儲返回結果。

另外一個比較重要的知識點是函數調用過程中與堆棧相關的寄存器RSP和RBP,兩個寄存器主要實現對棧位置的記錄,具體作用如下:

RSP:棧指針寄存器(reextended stack pointer),其內存放著一個指針,該指針永遠指向系統棧最上面一個棧幀的棧頂。

RBP:基址指針寄存器(reextended base pointer),其內存放著一個指針,該指針永遠指向系統棧最上面一個棧幀的底部。

寄存器的名稱跟體系結構是相關的,本文是64位系統,因此寄存器是RSP和RBP。如果是32位系統則寄存器的名稱為ESP和EBP。

應用程序調用棧

我們先從整體上來看一下函數調用棧的主要內容,如圖4所示。在函數棧中主要包括函數參數表、局部變量表、棧的基址和函數返回地址。這里棧的基址是上一個棧幀的基址,因為在本函數中需要使用該基址訪問棧中的內容,因此需要首先將上一個棧幀中的基址壓棧。

圖4 函數調用棧概覽

為了便于理解,我們以一個具體的程序作為示例。本程序非常簡單,主要是模擬了多個函數的函數調用關系和參數傳遞。另外,在函數func_2中定義了2個形參,以模擬多參數傳遞的過程。

圖5 函數棧匯編分析

在本示例中,main函數調用func_1函數。我們從main函數開始分析,可以先看一下右側的C語言代碼。首先是函數參數的準備過程。在main函數調用func_1時依次傳入的參數為1、2、3和4+g,其中最后一個參數是需要計算的。按照紅色方框的虛線,我們可以看到對應的匯編程序,在匯編程序中首先處理最后一個參數,然后是倒數第二個,以此類推(函數參數的處理順序在日常開發中是需要注意的內容重點)。同時,我們看到存儲參數的寄存器名稱與前文是一致。

當準備完參數之后,就是調用func_1函數,這個在匯編語言中就是call func_1這一行。雖然只是一行匯編指令,但其實內部做了一些事情,這個我們在前文介紹call指令的時候有所介紹,大家可以參考一下前文。

之后就進入func_1函數的處理邏輯。最一開始是pushq %rbp匯編程序,這句指令的作用是將RBP壓入函數棧中。這句壓棧及后面的更新RBP的值(moveq %rsp, %rbp)是構建本函數的棧幀頭,后續對本棧幀的內容的訪問都是通過幀頭(RBP)進行的。接下來是對參數壓棧的過程和局部變量初始化的過程,具體分布參考圖5中的綠色方框和紅色方框。

完成函數內的運算后,最后將運算結果放入寄存器EAX中,然后調用指令leave和ret。這里面需要說明的是leave指令,該指令相當于下面兩條匯編指令。可以對比一下函數入口的匯編指令,其實兩者是對稱的。leave指令將本幀的棧基址賦值給棧指針(圖6中步驟2),然后將其中的內容彈出到RBP中(圖6中步驟3)。其實就是RBP指向上一個幀(調用者)的棧幀,也即是一個復原的過程。

movl %ebp %esp
popl %ebp

圖6 函數返回示意圖

這樣,函數返回后寄存器RBP和RSP從被調用者的棧幀切換到了調用者的棧幀。

通過GDB分析函數調用棧

上面是通過反匯編的方式分析函數的調用棧和棧幀情況。我們還可以通過gdb動態的分析函數棧和棧幀的使用情況。我們依然通過main函數調用func_1函數為例來分析。我們這里在函數func_1的入口處設置一個單點,然后運行程序,程序停止在斷點處。如圖7是我們逐步執行是函數棧的變化過程,具體細節我們這里就不再贅述,大家可以實際操作一下。

圖7 函數棧變化過程

本文的目的是讓大家對函數調用棧有個整體的了解,這樣對以后程序的疑難雜癥就有更多的解決思路。因為在實際生產環境中與棧相關的問題也是比較多的,比如局部變量太多導致的棧溢出,或者踩內存問題引起的棧破壞等等。因此,了解了函數棧的原理,在遇到所謂的莫名其妙問題的時候就會有新的思路。往往很多問題不是問題本身莫名其妙,而是我們的知識儲備不夠,自己感覺莫名其妙而已。

責任編輯:龐桂玉 來源: 一口Linux
相關推薦

2019-06-23 17:37:58

Linux后端函數棧

2020-10-10 11:01:40

后端程序員技術

2020-10-09 14:44:57

程序員開發技術

2009-06-25 09:33:43

Java API程序員

2019-09-25 11:39:07

程序員編程技術

2022-10-24 09:00:47

畫圖工具程序員XMind

2014-08-15 14:25:48

Android程序員資源

2014-08-20 10:28:29

Android

2011-06-11 20:59:12

程序員

2013-06-09 09:56:35

2009-06-22 09:06:57

程序員技術升級

2011-07-19 13:27:35

2020-04-04 20:59:28

程序員技術開發

2015-07-09 10:30:35

程序員必備經驗

2020-01-12 19:10:30

Java程序員數據

2020-05-09 11:20:02

Java結構圖虛擬機

2020-10-14 08:32:08

算法遞歸面試

2021-03-02 09:34:15

GitHub倉庫代碼

2020-05-06 15:59:07

JavaScript程序員技術

2015-10-29 09:50:36

程序員免費編程圖書
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 久久久久久久一区 | 国产探花在线精品一区二区 | 国产精品一区二区三区四区 | 成人不卡 | 欧美99| 亚洲理论在线观看电影 | 国产精品久久久久久影院8一贰佰 | 亚洲一区二区久久 | 国产精品一区二区三区久久 | 爱爱无遮挡| 国产高清视频在线观看 | 日韩精品一区二区三区在线播放 | 欧洲成人 | 欧美一区二区三区在线观看 | 欧美伊人久久久久久久久影院 | 97免费视频在线观看 | 91 在线 | 丝袜 亚洲 欧美 日韩 综合 | 蜜桃特黄a∨片免费观看 | 最新高清无码专区 | 国产一区 | 综合久久久| 精品国产青草久久久久福利 | 在线亚洲免费视频 | 久久久国产一区 | 久久国产精品久久久久 | 亚洲精品国产一区 | 91精品国产一区 | 人人叉 | 午夜精品久久久久久久久久久久 | 国产午夜久久久 | 中文字幕欧美一区 | 色999视频 | 91福利网址 | 成人免费观看男女羞羞视频 | 老牛影视av一区二区在线观看 | 国产精品免费在线 | 浴室洗澡偷拍一区二区 | 高清亚洲 | 国产成人免费一区二区60岁 | 久久久青草婷婷精品综合日韩 |