本文檔由你們一塊兒自由編寫,修改和擴充,sniper負責維護。引用外來的文章要註明做者和來處。本文檔全部命令都是在ubuntu/debian下的操做。選取的內核源碼從文檔開始編寫時最新的內核版本–2.6.26開始,並且會隨着Linux的更新而不斷更換新的版本。因此文檔的內容可能先後不一致。相信你們有能力克服這個問題。php
本文檔的字符圖示在linux環境下顯示正常,在window下顯示有細微的錯亂。html
本文檔惟一的更新網址是:http://wiki.zh-kernel.org/sniper 轉載請保留此網址。前端
有任何建議請發郵件:s3c24xx@gmail.comjava
有任何問題請到郵件列表提問:http://zh-kernel.org/mailman/listinfo/linux-kernelnode
一我的默默地敲打這篇文章也有段時間了。在這個過程裏,沒有收到任何的讚譽,也沒接到任何的板磚,沒有任何的反饋。就這麼敲打着,修理着。可是本人從沒懷疑這篇文檔的價值,這是由於,本人就是這篇文檔的親身收益者。在這裏把它「無私」奉獻出來,乃是出於對於某類同道者持之以恆孜孜以求的「德性」的認同和「同情」,你的痛苦我表示感同身受,你的迷茫我願意一塊兒分擔。必定有人能從個文檔受益,這便已讓我滿足。其實,寫這個文檔並不是是件苦差,而是字字都是有感而發的,不吐不快的結果。這裏的句句都是本人教訓和經驗的記錄。python
談到調試器,世上存在兩種大相徑庭的見解。其中一種,是超級解霸的做者,他認爲「程序不是寫出來的,好程序絕對是調試出來的」。對於這個觀點,雖然本人學識淺陋,也很崇拜「 」他的爲人,可是本人仍是持着極不認同的態度。而第二種相反觀點的人,即是linux之父linus了。他認爲調試器只會「誤人子弟」,只會致使人們迷於表象而不去真正理解源碼自己。並以此爲由,長期沒把kgdb內置到內核中。對於調試器調試bug會引入錯誤的修正這個觀點,我認爲仍是有點道理的。可是他以此爲由而不把它集合到內核中,這個作法我就認爲是毫無道理了。由於linus本人就說過:「我只使用GDB,並且我老是並不把它做爲調試器來使用,只是將其做爲一個能夠用來分析程序的分解器來使用。」既然他能夠這樣作,爲何就認定他人使用gdb的目的必定就是用來調試bug而不是另有所用呢?本人之因此這樣說,這是由於本人正也是使用gdb主要是用來輔助分析內核代碼而不是主要用來調試錯誤的。這也正就是本文的主題。linux
世上從不缺乏解決問題的答案,缺乏的是解決問題的方法。如今,linux的世界裏已經不缺乏牛書了,將盡一千頁一本的滿載答案的磚頭書接踵而來,可是漸漸地發現,看書看到後面就忘了前面,回到前面有忘了後面,甚至一個章節還沒看完,那個子系統已經被徹底重寫了。慢慢地,就會懷疑「我是否是真的變老了?真的不行了?」可是咱們從沒想過:「憑什麼咱們就如此受制於人?他就能搞懂,而我就不行呢?」。其實,咱們須要的是一種重其意而忘其形的根本之道,須要的是一種兵來將擋,火來水淹的通用解決方法。而毫不是淹沒於牛人們的結論中。不然,遇到一個新的問題,就只能埋怨牛人的書還不夠厚,以致於沒把你須要的東西也包括進去了。牛人必定有一套牛方法,而他在書中不詳說,我不認爲是他故意「留一手」,而是認爲這是對自身以爲習覺得常的事物的一種疏忽。牛人的研究結果其實不是最重要的,他的研究方法和手段纔是最重要的事情。而我,也漸漸地發現,調試器能帶給咱們不少有用的提示,使得咱們能不斷的尋找到思考的靈感和方向,也使得學習變得很是的有趣性和有目的性。我想,利用調試器輔助源碼分析,是否是正是不少牛人正在作的而沒有說出來的事情呢?不管答案如何,本人仍是以爲,調試器是個好東西,不要輕易把它擱置在一旁。雖然不少高人也許已是深安此道,甚至已經不須要它的提示了,可是它依然有益於我等功力尚淺的人。把這種經驗和技巧記錄下來,讓須要這項技巧的人少化時間去摸索,這絕對不是一件壞事。c++
正是由於這個緣由,隨着文檔慢慢地變大,也更加的以爲文檔的題目起得有點不恰當了,題目起做「內核動態分析指南」更恰當點。文檔的主旨是利用調試器動態分析內核,調試錯誤只是這個過程的副產品罷了。不過,這個新的名字實在是不夠如今名字「刺眼」,因此也就沒有啓用它。git
說了這麼多的廢話和出格的話,無非是有兩個目的:這個文章慢慢的變得這麼長了,若是沒有半句的「人」話,沒有半句的現實世界中的語句。那估計本人不是變成了機器人,閱讀的人也會變成了機器人。順便借這段文字交交朋友。另外一個目的呢,是說不該拘束於工具,工具是死的,人是活的。若是某些工具確能帶給咱們某些有益的提示,咱們就能夠去嘗試它,取起優勢而舍其糟粕。web
引用的原文:
Linus 談調試器和內核如何發展: http://www.bitscn.com/linux/kernel/200604/7493.html
1. 永遠不要忘記的三大幫助命令
2. 如何安裝幫助文檔
3. 從軟件/工具的官方網站閱讀/下載文檔
4. 從irc獲取幫助 irc.freenode.NET
5. 從郵件列表獲取幫助 mailist http://lkml.org/ http://marc.info/
6. 發行版社區文檔或社區 https://help.ubuntu.com/community/ http://wiki.ubuntu.org.cn/
7. 利用google搜索文檔或閱讀他人文章
8. 利用google搜索lkml
http://www.google.cn/advanced_search?hl=zh-CN 網域那裏填上lkml.org
9. 獲取內核文檔
找到最新版本的文檔 $ apt-cache search linux-doc 安裝最新的文檔 $ sudo apt-get install linux-doc-2.6.24 閱讀Documentation/DocBook/ 下已經編譯好的書籍(html格式) $ firefox /usr/share/doc/linux-doc-2.6.24/html/index.html
10. 買書
11. 書籍最後面的參考書目
12. 文章末尾的參考文章
todo:學習方法,學習曲線,參考書籍的特色和不足,本文檔的任務
內核學習曲線
1.只讀書不看源碼
參考書籍:Linux Kernel Development
2.參考源碼讀書(讀書爲主)
參考書籍:understanding the linux kernel
3.參考書讀源碼(看源碼爲主)
參考書籍:情景分析
4.只看源碼不/少讀書(提交補丁爲主)
參考:lkml,main-tree, mm-tree
linux內核分析方法:
按分析的對象分:
1.代碼: 分析的對象是源代碼
2.數據: 分析的對象是內核運行時產生的數據
按觀察對象的狀態分:
1.靜態: 觀察的目標對象是靜止不動的
2.動態: 觀察的目標對象是動態變化的
因此綜合地看,分析方法的種類有:
1.靜態代碼:
最原始的方式,閱讀源代碼
2.動態代碼:
利用某些工具或手段,動態分析源代碼。又分爲
a. 利用lxr, cscope, source insight等工具交叉索引源代碼
b. 利用Git,web-git經過閱讀增量patch等形式觀察源碼的進化
c. 利用調試器跟隨內核的運行動態觀察內核正在運行的代碼片斷
3.靜態數據:
觀察的對象是內核在運行時產生或收集彙總出來的數據。又分爲
a. 代碼中printk語句打印出來的內核信息
b. 系統出錯產生的oops,panic信息
c. 藉助systemtap等相似工具提取的內核數據彙總
4.動態數據:
藉助內核調試器實時觀察內核不斷產生的數據
可見內核調試器是最強大的內核分析工具,但它也不是「全功能」的工具。
1. 主要地,本文檔聚焦於描述如何利用gdb對內核進行源碼級別和彙編級別的觀察和調試。
而這種調試的目的有兩個:
前者是調試器應用的主要價值,然後者倒是本文檔的興趣所在。
2. 由於須要觀察用戶層和內核層的交互,演示調試工具的全面功能等緣由,本文檔內容不徹底侷限於內核層。
3. 另外,爲了提供內核調試知識的全面敘述,咱們對其餘調試工具,其餘調試的問題好比檢測內存泄露等內容,也會進行說明。此部份內容放於本文檔的第三部分。
例子1:NT 內核的進程調度分析筆記 http://www.whitecell.org/list.PHP?id=11
例子2: NT 下動態切換進程分析筆記 http://www.whitecell.org/list.php?id=13
在windows的世界裏,內核源碼和具體原理是不公開的。但不少牛人就憑一個破爛調試器閱讀反彙編代碼就能獲得內部真相,可見調試器彙編級調試威力之大。可是在linux是源碼公開的狀況下,就不必幹那樣的辛苦活了。可是由於如下緣由,彙編級調試仍是必要的。
有時(好比代碼優化)狀況下,由於C代碼通過了編譯器的處理,調試器在c源碼調試這個級別下給出的信息是沒法理解的,甚至看起來是錯誤的。可是若是直接對調試器給出的反彙編代碼進行分析,就不會受到那類問題的束縛。也就是說,進行彙編級別的調試能最大程度的利用調試器的功能。
當你對某句C語言不是很理解時,看看編譯器是怎麼想的,是個很不錯的辦法。
另外一方面,內核中原本存在不少彙編源代碼,進行彙編級調試也是鍛鍊閱讀彙編源碼能力的最有效方法。
固然,彙編級調試雖然強大,但代價也是很昂貴。和源碼級調試相比,分析彙編代碼花的時間要多上幾十倍。因此,在源碼公開的狀況下,應該以源碼級調試爲主,特殊狀況下才須要彙編級調試。
也是閱讀理解其餘任何大型代碼會遇到的問題。下面各節的內容都是圍繞這些小項展開的。若是有的內容不知所云,先看後面內容,再回頭看這裏。
[先從其餘地方複製過來,等待充實]
源碼不可是愈來愈大,更是愈來愈「刁」了。「刁」到了就是藉助源碼交叉索引工具也有它索引不到的地方。因此目前,即便是從源碼閱讀的角度而不是從調試的角度,只利用閱讀工具不借助調試工具的話,源碼都沒法閱讀。
源碼「刁」到源碼解析工具都沒法解析的因素有:
1. 彙編源碼包括內嵌彙編 可能沒法被你的源碼閱讀工具所解析
2. 彙編代碼和C代碼之間的調用關係 沒法被被源碼閱讀工具解析
3. 利用函數指針的函數調用 沒法被被源碼閱讀工具解析
4. 宏「假函數」 可能沒法被被源碼閱讀工具解析(SI不能解析,lxr能)
好比page_buffers()。定義是: #define page_buffers(page) / ({ / BUG_ON(!PagePrivate(page)); / ((struct buffer_head *)page_private(page)); / })
5. 利用宏在編譯時動態生成的函數體 沒法被被源碼閱讀工具解析
好比fs/buffer.c中有一大批相似函數。好比buffer_unwritten() 定義在buffer_head.h 82 #define BUFFER_FNS(bit, name) / ..省略 91 static inline int buffer_##name(const struct buffer_head *bh) / 92 { / 93 return test_bit(BH_##bit, &(bh)->b_state); / 94 } .. 130 BUFFER_FNS(Unwritten, unwritten) 這類函數通常是短小的內嵌函數,用gdb調試時都看不出來。只能靠字符搜索再加上一點機靈。
6. 函數/變量的某類c擴展屬性標記, 可能致使該函數/變量沒法被被源碼閱讀工具解析
好比static struct vfsmount *bd_mnt __read_mostly;中的bd_mnt
7. 其餘語種的保留關鍵字,可能沒法被你的源碼閱讀工具所解析
如默認配置的SI沒法解析struct class,固然,這個問題和內核無關。
可是藉助調試器,就能直接而輕易地解決上述源碼解析工具難以解決的問題。
搭建調試環境
gdb調試器的陷阱
1. 宏「假函數」
2. 內嵌函數
3. 代碼優化
4. 彙編碼
5. 進程切換
6. 中斷處理
7. 系統調用
0. 連接器腳本和make語法
下面這些雜七雜八的文件對內核總體原理的理解起着決定性的做用。 內核中的連接腳本 linux-2.6$ find ./ -name "*lds*" 內核中的重要宏文件 module_param* macros include/linux/moduleparam.h *__initcall Macros include/linux/init.h 內核中的彙編文件 linux-2.6$ find ./ -name "*.S" 內核中的Makefile linux-2.6$ find ./ -name "Makefile" 內核中的配置文件 linux-2.6$ find ./ -name "*config*"
1. C與彙編代碼的相互調用
2. 各子系統間的接口互動
3. 內核的設計思想及其代碼編寫和運行形式
a) 基於對象的思想
例子:文件系統,設備模型
b) 「發佈—訂閱」模型
例子:notification chain
[如題] http://www.debian.org/ http://www.emdebian.org/
爲何本人選擇debian?由於:引用內容來之www.debian.org
「Debian 計劃 是一個致力於建立一個自由操做系統的合做組織。...屁話省略...屁話..N多屁話以後: 固然,人們真正須要的是應用軟件,也就是幫助他們完成工做的程序: 從文檔編輯,到電子商務,到遊戲娛樂,到軟件開發。Debian 帶來了超過 18733 個 軟件包 (爲了能在您的機器上輕鬆的安裝,這些軟件包都已經被編譯包裝爲一種方便的格式) — 這些所有都是 自由 軟件。」
緣由終於看到了,選擇debian是由於本人比較懶,比較笨。而debian正好迎合了我這種人的需求。
1. 它」帶來了超過 18733 個 軟件包」。18733這個數目很是不直觀,並且或許是N年前的數據了。咱們能夠到debian的ftp看看,如今它可供安裝的軟件和工具達到了5個DVD的容量。不可思議,在這5個DVD容量的工具庫中,還會找不到我所想要的東西。
2. debian有一個很是出名的安裝包管理機制。你須要作的就是,打開「立新得」軟件,而後在一個小方框裏寫上你須要東西的相關信息,而後再點點一個叫作「搜索」的小方塊。接着,debian就會在它5個DVD大的工具庫中尋找你想要的工具。在結果返回後,選擇好你的工具,再點點一個叫作「應用」的小方塊,過一會,就能夠使用你的工具了。
再也沒有了「缺乏什麼什麼包」的煩人提示了,一切都這麼簡單,又這麼強大。這,正是我想要的。
[二者區別,版本外號,支持社區,source list等] 1. ubuntu的易用性比debian要好。尤爲是中文支持,還有ubuntu國內有活躍的社區。 2. 雖然ubuntu是基於debian的,apt 軟件庫也能獲取到debian的軟件,但它畢竟是不一樣的系統環境,理念不一樣,對於一些偏門或太舊或太新的軟件時,ubuntu每每不支持,安裝不了。好比,gcc-3.4-arm-linux-gnu這個包,發行時間已久,ubuntu下安裝不了,但在debian下則能夠。http://www.ubuntu.com/community/ubuntustory/debian
如不特別說明,本文檔全部命令都是在ubuntu Hardy Heron8.04版本 和debian testing版本下的操做。
[若是想領教古典linux相對於windows的特點,請安裝一次debian吧。儘管和之前比,已經很智能了。但安裝了debian,選了中文環境,發現漢字都是歪歪倒倒的。並且沒有漢字輸入法,裝了漢字輸入法後,卻用不了。不知道是我笨仍是程序有bug.因此不得不用英文寫下本爛文,怕把安裝過程給忘了。須要翻譯回中文]
How to install and configure a debian system from zero
1.install the system with one CD
Download CD iso file from debian official website, and burn it into a CD. Note that, we can just download the first CD iso but not DVDs or the whole serials of CDs, because the first CD has already contained all the basis components of dedian system and many other most common applications. We can use the first CD to install debian system, and then to install some other needed programs from it if needed. In this way, you can save much time spent on touching many inrelatived things.
2.install application & tool from CD
ou can install some common apllications from the CD with the following commnad: apt-get install expected-application. Why can we do that without any more configuration? Why is it not need to has a ability to access internet? Well, Let’s look at the file named sourse.list which idenifying where to get software’s pakage?? deb cdrom:[Debian GNU/Linux testing _Lenny_ - Official Snapshot i386 CD Binary-1 20080605-15:01]/ lenny main It means that system try to get somethig from your CD, so obviously that you can get some the most common but not all the tools available in debian official apllication repository.
3.try to access the internet
Thank to the first CD, we can do that easily. Fist, install the tool ppp contained in CD and its’ configuration tool pppoeconfig. All these steps are described in file ADSL(PPPOE)接入指南.txt
4.search any useful information through the internet
now, we have built a base debian system, but it is too simple. I want to do some some thing, for example, to chat with some other people with pidgin, but it is not contained in the first CD, which just downloaded by you. And you may want to search some helps with google,etc. Just to do it, google is a most useful tool.
5.search the internet updating source
I think you have get much thing through the google. But the most important thing is to get a available update source for your system, and change the source.list–that is /etc/apt/source.list. Now, I have got a good one, and it seems good. Don’t forget to turn on the security entry in the orgion file source.list. That file looks like following after my updataion:
#deb cdrom:[Debian GNU/Linux testing _Lenny_ - Official Snapshot i386 CD Binary-1 20080605-15:01]/ lenny main deb http://ftp.debian.org/debian/ lenny main contrib non-free deb http://security.debian.org/ lenny/updates main deb-src http://security.debian.org/ lenny/updates main
You should note that the internet address is debian office’s, but It takes some while to get it. And my searching tool is google. :) Oh, we shoul run a command to update the new configuration to system before using it, don’t ferget: apt-get update
6.get help from IRC
Well, we have already been able to get some applications or tools from internet with command apte-get or wget,etc.. But I think the first thing to do is to get and install a very valuable tool named pidgin which can bring you intoIRC world. Because Many experiance and kind person live in channel #debian of irc.freenode.Net. You can get help from it very quickly. How to configure pidgin? Sorry, I don’t like to answer such a problem , please just to google it or try it by yourselft. I am not so kind as some guys living inIRC : )
7.get and install synaptic
If you ever used ubuntu, you should agree that synaptic is good tool to update you system. It can save you much time of searching tools, typing commnad, or managing the downloaded tools. But Unfortunately, such a important tool is not installed in the default system, and it is not contained in the first CD. So, We can just to get it with command 「apt-get install synaptic」. After doing that successfully, I don’t want to type that command anymore. It’s so tedious to me.
8.get more tools with the help of synaptic
synaptic is my GOD in the linux world. Without it, I will become crazy. But now, I have owned it, so I can fly very freely in the internet sky. Just to search any tools and to update your system. And now, the CD used to install debian can be discarded, if you will never reinstall or rescure the system with it in future.
Now, the sun has raise up, and you have found the road to reback to civilization. Why? Just to ask your google and synaptic. :)
[來源]《APT and Dpkg 快速參考表》 http://i18n.linux.net.cn/others/APT_and_Dpkg.php
Apt 不止是 apt-get
默認安裝的debian,鍵盤的設置可能有問題。好比「|」打不出來。值得一提的是,這個設置甚至是和qemu的monitor模式相關聯的。也就是說,qemu下有的字符也打不出來。若是有這個問題,按下面步驟設置
System→Preferences→Keyboard→Layouts
而後經過「Add」增長China,並設置它爲默認,或者同時把其餘的刪除掉。
說明,中文環境比英文環境有不少缺點。好比編譯時編譯器的提示都給漢化了,有如,minicom的中文漢化界面是錯亂的,並且minicom沒法設置。本人通常是英文環境+中文輸入法。先安裝好好中文環境,系統中就有了中文輸入法和其餘一些和中文有關的東西。而後轉到英文環境下,按照下面作法更改scim的配置文件便可。
編輯 /etc/gtk-2.0/gtk.immodules(若是存在的話) 或者 /usr/lib/gtk-2.0/2.10.0/immodule-files.d/libgtk2.0-0.immodules 文件,在xim 的 local 增長 en 也就是說:
"xim" "X Input Method" "gtk20" "/usr/share/locale" "ko:ja:th:zh" 改爲: "xim" "X Input Method" "gtk20" "/usr/share/locale" "en:ko:ja:th:zh" 注意,必定要重啓一下機器。
$sudo apt-get install xpdf-chinese-simplified xpdf-chinese-traditional poppler-data
參考:
http://wiki.ubuntu.org.cn/PDF%E6%96%87%E6%A1%A3%E7%9A%84%E4%B9%B1%E7%A0%81%E9%97%AE%E9%A2%98
$ sudo apt-get install build-essential autoconf automake1.9 cvs subversion libncurses5-dev git rar unrar p7zip-full cabextract
其他的根據出錯的提示,利用「立新得」搜索,而後進行安裝。沒有「立新得」界面程序的能夠在終端下利用如下命令來搜索和安裝。
$ sudo apt-get update $ apt-cache search XXX $ sudo apt-get install XXX
雙硬盤系統切換設置, 私人備忘用
title Microsoft Windows XP Professional root (hd1,0) savedefault makeactive map (hd0) (hd1) map (hd1) (hd0) chainloader +1
下面是幾個交叉編譯工具下載網址,須要手動安裝時,對比一下編譯器的名稱能夠找到合適的下載地址。debian維護有本身的已經打包成.deb形式安裝包,在debian軟件庫中。
http://www.codesourcery.com/gnu_toolchains/arm/download.html (聽說是arm公司推薦的) Download Sourcery G++ Lite Edition for ARM Target OS Download EABI Sourcery G++ Lite 2008q1-126 All versions... uClinux Sourcery G++ Lite 2008q1-152 All versions... GNU/Linux Sourcery G++ Lite 2008q1-126 All versions... SymbianOS Sourcery G++ Lite 2008q1-126 All versions... 究竟是選EABI仍是GNU/LINUX呢?應該是後者.... 點GNU/LINUX的鏈接進去,可看到 Download MD5 Checksum IA32 GNU/Linux Installer 93eee13a08dd739811cd9b9b3e2b3212 IA32 Windows Installer fac5b0cee1d9639c9f15e018e6d272ad Documentation Title Format Assembler (PDF) PDF Binary Utilities (PDF) PDF C Library (GLIBC) (PDF) PDF Compiler (PDF) PDF Debugger (PDF) PDF Getting Started Guide (PDF) PDF Linker (PDF) PDF Preprocessor (PDF) PDF Profiler (PDF) PDF Advanced Packages Expert users may prefer packages in these formats. Download MD5 Checksum IA32 GNU/Linux TAR 4f11b0fa881864f220ab1bd84666108b IA32 Windows TAR ed6d25fd68301e728a1fba4cd5cb913f Source TAR 2db28fb2aa80134e7d34d42b7039d866 名字標識不是很明顯,進去看才知道。好比,IA32 GNU/Linux Installer對應的安裝包 名字叫arm-2008q1-126-arm-none-linux-gnueabi.bin 爲何有個none?迷茫中.. --------------------------------- http://ftp.snapgear.org:9981/pub/snapgear/tools/arm-linux/ [DIR] Parent Directory 30-Sep-2003 15:44 - [ ] arm-linux-tools-20031127.tar.gz 26-Nov-2007 16:56 141M [ ] arm-linux-tools-20051123.tar.gz 24-Nov-2005 00:50 228M [ ] arm-linux-tools-20061213.tar.gz 13-Dec-2006 13:31 230M [ ] arm-linux-tools-20070808.tar.gz 30-Nov-2007 03:21 271M [ ] binutils-2.16.tar.gz 16-Nov-2005 15:44 15.6M [ ] binutils-2.17.tar.gz 06-Dec-2007 10:24 17.4M [ ] build-arm-linux-3.4.4 02-Aug-2006 14:32 6k [ ] build-arm-linux-4.2.1 30-Jul-2008 10:13 7k [ ] elf2flt-20060707.tar.gz 17-Jan-2008 22:23 101k [ ] elf2flt-20060708.tar.gz 30-Jul-2008 10:14 110k [ ] gcc-3.4.4.tar.bz2 16-Nov-2005 15:39 26.3M [ ] gcc-4.2.1.tar.bz2 06-Dec-2007 10:11 42.0M [ ] genext2fs-1.3.tar.gz 03-Sep-2003 10:23 19k [ ] glibc-2.3.3.tar.gz 16-Nov-2005 15:49 16.7M [ ] glibc-2.3.6.tar.gz 06-Dec-2007 10:39 17.9M [ ] glibc-linuxthreads-2.3.3.tar.gz 16-Nov-2005 15:49 303k [ ] glibc-linuxthreads-2.3.6.tar.gz 06-Dec-2007 10:39 320k -------------------------- http://www.handhelds.org/download/projects/toolchain/ [DIR] Parent Directory - [ ] README 28-Jul-2004 17:37 788 [DIR] archive/ 28-Jul-2004 17:34 - [ ] arm-linux-gcc-3.3.2.tar.bz2 03-Nov-2003 10:23 71M [ ] arm-linux-gcc-3.4.1.tar.bz2 29-Jul-2004 14:01 41M [DIR] beta/ 28-Jul-2004 17:36 - [ ] crosstool-0.27-gcc3.4.1.tar.gz 28-Jul-2004 17:21 2.0M [ ] gcc-build-cross-3.3 31-Oct-2003 15:43 5.1K [DIR] jacques/ 24-Jul-2001 18:45 - [ ] kernel-headers-sa-2.4.19-rmk6-pxa1-hh5.tar.gz 12-Mar-2003 17:42 4.7M [DIR] monmotha/ 13-Aug-2002 17:54 - [DIR] osx/ 14-Dec-2003 11:45 - [DIR] pb/ 22-Nov-2002 20:10 - [DIR] source/ 18-Mar-2004 16:12 - ------------------------------------ http://ftp.arm.linux.org.uk/pub/armlinux/toolchain/ [DIR] Parent Directory - [ ] Oerlikon-DevKit-XScalev2.tar.gz 07-Feb-2003 22:30 3.7K [ ] cross-2.95.3.tar.bz2 20-Jul-2001 21:12 35M [ ] cross-3.0.tar.bz2 20-Jul-2001 22:27 39M [ ] cross-3.2.tar.bz2 23-Aug-2002 11:04 81M [ ] cross-3.2.tar.gz 23-Aug-2002 10:01 93M [DIR] src-2.95.3/ 14-Jan-2002 17:52 - [DIR] src-3.2/ 23-Aug-2002 10:53 - -------------------------------------------- http://linux.omap.com/pub/toolchain/ [DIR] Parent Directory - [ ] obsolete-gcc-3.3.2.t..> 15-May-2004 12:18 76M --------------------------- http://www.uclinux.org/pub/uClinux/arm-elf-tools/ To install the Linux binaries, login as root and run "sh ./XXX-elf-tools-20030314.sh". m68k-elf-20030314/arm-elf-20030314 Get the m68k binaries or the ARM binaries. The source is here. m68k-elf-20020410/arm-elf-20011219 Get the m68k binaries or the ARM binaries. The source is here. m68k-elf-20020218/arm-elf-20011219 Get the m68k binaries or the ARM binaries. The source is here. m68k/arm-elf-20011219 Get the m68k binaries or the ARM binaries. The source is here. You can also get Bernhard Kuhn's RPMs here. m68k-elf-20010716 Get the binaries here and the source from here. m68k-elf-20010712 Get the binaries here and the source from here. m68k-elf-20010610 Get the binaries here and the source from here. m68k-elf-20010228 The binaries are in two files, the compilers and the g++ headers. The source is here.
debian有本身維護的一套交叉編譯工具集
[參考]http://www.emdebian.org/tools/crosstools.html
工具庫: http://www.emdebian.org/debian/pool/main/
步驟:
1. 往/etc/apt/sources.list文件加入下面軟件源
deb http://buildd.emdebian.org/debian/ unstable main deb-src http://buildd.emdebian.org/debian/ unstable main deb http://buildd.emdebian.org/debian/ testing main deb-src http://buildd.emdebian.org/debian/ testing main
而後:
安裝 emdebian-archive-keyring package $ sudo apt-get install emdebian-archive-keyring 更新 $ sudo apt-get update
2. 安裝交叉編譯器
$ sudo apt-get install libc6-armel-cross libc6-dev-armel-cross binutils-arm-linux-gnueabi gcc-4.3-arm-linux-gnueabi g++-4.3-arm-linux-gnueabi
注意,在ubuntu8.04下,只能安裝4.2版。把上面文字中的4.3所有換爲4.2便可。
3. 安裝交叉調試器
$sudo apt-get install gdb-arm-linux-gnueabi
注意:
a. 安裝時使用名稱:gdb-arm-linux-gnueabi,調用時使用命令名是:arm-linux-gnueabi-gdb
b. ubuntu下,arm-linux-gnueabi-gdb和gdb有衝突。
解決方法:
須要使用arm-linux-gnueabi-gdb時先卸載gdb,記下卸載gdb時與gdb一塊兒被卸載的軟件名,而後安裝arm-linux-gnueabi-gdb。 想換回gdb時,在反操做。apt-install remove arm-linux-gnueabi-gdb 而後 apt-get install gdb以及以前和gdb一塊兒被卸載包。能夠寫個腳本自動完成這些操做。本人環境下的腳本是:
腳本1. install-armgdb.sh
#! /bin/sh sudo apt-get remove gdb sudo apt-get install gdb-arm-linux-gnueabi
腳本2. install-gdb.sh
#! /bin/sh sudo apt-get remove gdb-arm-linux-gnueabi sudo apt-get install apport apport-gtk apport-qt bug-buddy cgdb gdb python-apport xxgdb
答: 來自AAPCS
ABI: Application Binary Interface:
1). The specifications to which an executable must conform in order to execute in a specific execution environment. For example, the Linux ABI for the ARM Architecture.
2). A particular aspect of the specifications to which independently produced relocatable files must conform in order to be statically linkable and executable. For example, the C++ ABI for the ARM Architecture, the Run-time ABI for the ARM Architecture, the C Library ABI for the ARM Architecture.
ARM-based … based on the ARM architecture …
EABI: An ABI suited to the needs of embedded (sometimes called free standing) applications.
參考:
ABI/EABI/OABI http://blog.csdn.net/hongjiujing/archive/2008/07/21/2686556.aspx
Re: 關於kernel ARM_EABI http://zh-kernel.org/pipermail/linux-kernel/2008-January/002793.html
Why ARM’s EABI matters http://www.linuxdevices.com/articles/AT5920399313.html
Why switch to EABI? http://www.applieddata.net/forums/topic.asp?TOPIC_ID=2305
ArmEabiPort http://wiki.debian.org/ArmEabiPort
注:arm-elf-XXX 工具集是用於uclinux的
1. 依據要求搜索下載相應的arm-elf-tools安裝包。好比arm-elf-tools-20030315.sh
2. 安裝: $ ./arm-elf-tools-20030315.sh
3. 若是,該安裝包年代過老,好比arm-elf-tools-20030315.sh,會出現下面的錯誤提示 「tail: 沒法打開「 43」 讀取數據: 沒有那個文件或目錄。」。 這時須要修改安裝包源碼。方法:vi arm-elf-tools-20030315.sh, 搜索tail,在它後面加 -n .好比 把tail ${SKIP} ${SCRIPT} | gunzip | tar xvf -改爲以下:tail -n ${SKIP} ${SCRIPT} | gunzip | tar xvf -
4.如何卸載已安裝的arm-elf-tools? 答,從新安裝一次,注意看終端提示。或直接vi arm-elf-tools-20030315.sh,看腳本的內容,
[該怎麼稱呼這類工具?待詳述]
arm-elf-addr2line arm-elf-elf2flt arm-elf-gdb arm-elf-objdump arm-elf-size arm-elf-ar arm-elf-flthdr arm-elf-ld arm-elf-protoize arm-elf-strings arm-elf-as arm-elf-g++ arm-elf-ld.real arm-elf-ranlib arm-elf-strip arm-elf-c++ arm-elf-gasp arm-elf-nm arm-elf-readelf arm-elf-unprotoize arm-elf-c++filt arm-elf-gcc arm-elf-objcopy arm-elf-run arm-linux-gnueabi-addr2line arm-linux-gnueabi-g++ arm-linux-gnueabi-gprof arm-linux-gnueabi-readelf arm-linux-gnueabi-ar arm-linux-gnueabi-g++-4.2 arm-linux-gnueabi-ld arm-linux-gnueabi-size arm-linux-gnueabi-as arm-linux-gnueabi-gcc arm-linux-gnueabi-nm arm-linux-gnueabi-strings arm-linux-gnueabi-c++filt arm-linux-gnueabi-gcc-4.2 arm-linux-gnueabi-objcopy arm-linux-gnueabi-strip arm-linux-gnueabi-cpp arm-linux-gnueabi-gdb arm-linux-gnueabi-objdump arm-linux-gnueabi-cpp-4.2 arm-linux-gnueabi-gdbtui arm-linux-gnueabi-ranlib
如何獲取這些工具的命令選項? 看章節「知識從哪裏來」 通常是用命 xxxxxx –help就能獲得簡單的命令選項列表
下載arm-linux-gnueabi- 手冊地址 http://www.codesourcery.com/gnu_toolchains/arm/portal/release324
而後搜索」arm」,便能找處處理器相關的特殊命令選項
查看arm處理器相關的編譯選項
$ vi arch/arm/Makefile
閱讀Makefile文件,並聯系源碼根目錄下的.config文件,便能知道arm-linux-gnueabi-gcc用了哪些編譯選項。再到手冊中查找,便能知道這些選項是幹什麼用的,但手冊中說的不是很詳細。另外查找有用解釋的方法的是,利用make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- menuconfig,找到與命令選項有關聯的CONFIG_XXX的菜單項,看它的幫助說明.好比
$ vi arch/arm/Makefile .... ifeq ($(CONFIG_AEABI),y) CFLAGS_ABI :=-mabi=aapcs-linux -mno-thumb-interwork else CFLAGS_ABI :=$(call cc-option,-mapcs-32,-mabi=apcs-gnu) $(call cc-option,-mno-thumb-interwork,) endif ..
再查看CONFIG_AEABI的幫助文檔 $ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- menuconfig 找到CONFIG_AEABI相關的菜單,看它的幫助文檔,便能知道選項-mabi=aapcs-linux -mno-thumb-interwork的總體效果怎樣的。
┌───────────────────── Use the ARM EABI to compile the kernel ──────────────────────┐ │ CONFIG_AEABI: │ │ │ │ This option allows for the kernel to be compiled using the latest │ │ ARM ABI (aka EABI). This is only useful if you are using a user │ │ space environment that is also compiled with EABI. │ │ │ │ Since there are major incompatibilities between the legacy ABI and │ │ EABI, especially with regard to structure member alignment, this │ │ option also changes the kernel syscall calling convention to │ │ disambiguate both ABIs and allow for backward compatibility support │ │ (selected with CONFIG_OABI_COMPAT). │ │ │ │ To use this you need GCC version 4.0.0 or later. │ │ │ │ Symbol: AEABI [=n] │ │ Prompt: Use the ARM EABI to compile the kernel │ │ Defined at arch/arm/Kconfig:554 │ │ Location: │ │ -> Kernel Features
arm-linux-gnueabi-gcc的主要編譯選項有以下幾個。可是在編譯內核時,這些選項是不須要手工去寫的,而是經過make menuconfig生成包含了編譯選項配置信息的.config文件。在make編譯內核時,再利用Makefile文件中的規則結合.config文件提取出那些選項。
太多了,手冊吧
注意它的默認選項設置
$ arm-linux-gnueabi-gdb (gdb) show arm abi: The current ARM ABI is "auto" (currently "APCS"). apcs32: Usage of ARM 32-bit mode is on. disassembler: The disassembly style is "std". fpu: The current ARM floating point model is "auto" (currently "fpa"). (gdb)
可是,若是若是在命令後有參數vmlinux的話,它會自動識別出內核的abi,從而自動設置了gdb的abi。好比,在編譯內核時,若是選了CONFIG_AEABI,則gdb的提示以下
$ arm-linux-gnueabi-gdb vmlinux ... (gdb) show arm abi: The current ARM ABI is "auto" (currently "AAPCS"). <--注意 apcs32: Usage of ARM 32-bit mode is on. disassembler: The disassembly style is "std". fpu: The current ARM floating point model is "auto" (currently "softvfp").
參考手冊
http://bellard.org/qemu/user-doc.html
http://wiki.debian.org.tw/index.php/QEMU
http://www.h7.dion.ne.jp/~qemu-win/
郵件列表
http://lists.gnu.org/archive/html/qemu-devel/
參考文章
「QEMU安裝使用全攻略」 http://forum.ubuntu.org.cn/viewtopic.php?p=248267&sid=f4e95025bdaf6a24a218315d03ad9933
[補充命令]引用自http://bbs.chinaunix.net/viewthread.php?tid=779540
安裝過程當中,要求換盤: 在qemu中按ctrl+alt+2切換到qemu monitor模式 輸入?或help能夠查看可用命令及使用說明。 (在其餘版本的qemu中,運行qemu加載OS後,這個shell就會自動變成qemu monitor模式) change device filename -- change a removable media 看來它就是用來換盤的了 : change cdrom /rhel4/EL_disc2.iso 切換回安裝界面ctrl+alt+1 monitor下還有幾個經常使用的命令: savevm filename 將整個虛擬機當前狀態保存起來 loadvm filename 恢復 (最初我沒用change換盤時,就是先savevm->從新運行qemu->loadvm ) sendkey keys 向VM中發送按鍵,例如你想在虛擬機裏切換到另外一個終端,按下了ctrl-alt-F2 不幸的是,切換的倒是你的主系統,因此就須要用 sendkey了 sendkey ctrl-alt-f2 還有其餘幾個命令,本身看看啦。 通過N久終於裝好了,如今能夠啓動試試: [root@LFS distro]#qemu redhat.img -enable-audio -user-net -m 64 -user-net 至關於VMware的nat,主系統能夠上,虛擬機就能夠 -m 64 使用64M內存,缺省下使用128M ctrl-alt-f 全屏 ctrl-alt 主機/虛擬機鼠標切換 qemu還有一些其餘參數,輸入qemu能夠查看其相關說明
[擴展,原理,相關命令。下面的skyeye可能須要這部分知識]
「Linux2.6 內核的 Initrd 機制解析」 http://www.ibm.com/developerworks/cn/linux/l-k26initrd/
「Introducing initramfs, a new model for initial RAM disks」 http://www.linuxdevices.com/articles/AT4017834659.html
」」深刻理解 Linux 2.6 的 initramfs 機制 (上)「 http://blog.linux.org.tw/~jserv/archives/001954.html
MKINITRAMFS http://www.manpage.org/cgi-bin/man/man2html?8+mkinitramfs
$ sudo apt-get install initramfs-tools $ mkinitramfs /lib/modules/2.6.26/ -o initrd.img-2.6.26
參考
「debugging-linux-kernel-without-kgdb」 http://memyselfandtaco.blogspot.com/2008/06/debugging-linux-kernel-without-kgdb.html
「使用 KGDB 調試 Linux 內核」 http://blog.chinaunix.net/u/8057/showart_1087126.html
「透過虛擬化技術體驗 kgdb (1)」 http://blog.linux.org.tw/~jserv/archives/002045.html
缺點:相對於下節的「基於qemu和qemu內置gdbstub」,這個方法配置麻煩。
優勢:真機遠程調試時只能使用內置kgdb這個方法。
[等待擴展,,,,]
終極參考
「Using kgdb and the kgdb Internals」 http://www.kernel.org/pub/linux/kernel/people/jwessel/kgdb/index.html
參考文章
「使用 KGDB 調試 Linux 內核」 http://blog.chinaunix.net/u/8057/showart_1087126.html
「Debugging Linux Kernel Without KGDB Patch (Qemu + GDB)」http://memyselfandtaco.blogspot.com/2008/06/debugging-linux-kernel-without-kgdb.html
優勢:相對上節,優勢是操做簡單,幾乎不須要什麼配置
缺點:真機的遠程調試,就只能利用內核的內置kgdb了
說明:
若是長時間調試固定版本的內核,採起下面的把調試用內核安裝的虛擬機內部就能夠了。可是若是是要頻繁地更換新內核或修改被調試內核,就須要採起把內核掛在虛擬機外部的形式。也就是用 -kernel 在虛擬機外面掛個內核, 再利用-append 傳遞起內核啓動參數等。[待研究]
[太概過了,待擴展...]
1. 利用qemu安裝一個系統.
2. 在真機中配置並編譯一個用於安裝到虛擬系統中的新內核,注意配置時的選擇
* 配置和啓動 1. 內核選項 同時,爲了能在系統運行時中斷系統並出發遠程 gdb,必須打開內核 Magic Sys-Rq 鍵選項 :[後記,沒實驗去掉會怎樣,估計沒影響] CONFIG_MAGIC_SYSRQ=y 打開內核符號調試: CONFIG_DEBUG_INFO=y
3. 在真機下編譯好虛擬機新內核的源碼
4. 結束qemu,用如下命令在真機上掛載虛擬硬盤。而後把編譯好的整個源碼目錄都拷貝到掛載好的虛擬硬盤上(真機上保留一份源碼)。
$ sudo mount -o loop,offset=32256 debian.img /mnt
拷貝完後,在真機上卸載虛擬硬盤
$ sudo umount /mnt
5.啓動虛擬機,進入舊系統,在新內核源碼根目錄下用如下命令給qemu的虛擬系統安裝一個新的內核
拷貝模塊 $ make modules_install 安裝內核 $ make install 製做initrd.img $ cd /boot $ mkinitramfs /lib/modules/2.6.26/ -o initrd.img-2.6.26 檢查/boot/grub/menu.lst 文件內容是否穩當
6.用如下命令重啓虛擬系統,並選擇進入新系統,確認新系統是否安裝成功。
$ shutdown -r now
1. 在真機新內核源碼目錄下創建一個文件 .gdbinit 內容是
target remote localhost:1234 b start_kernel #c
注意我把c註釋掉是由於ddd和gdb有切換的須要。見」gdb技巧」
2. 用如下命令啓動虛擬機
qemu -hda debian.img -cdrom ../debian-testing-i386-CD-1.iso -m 500 -S -s
3. 在真機新內核源碼目錄下運行
gdb ./vmlinux
[實驗記錄]
實驗過了,.config中不選擇kgdb,利用qemu照樣能調試。也不能調試start_kernel之前的代碼。好比head_32.S中的代碼。
CONFIG_HAVE_ARCH_KGDB=y # CONFIG_KGDB is not set
可是不知CONFIG_HAVE_ARCH_KGDB是在menuconfig菜單的哪裏。想試試把這項去了qemu還能不能調試。
經測試,取消CONFIG_HAVE_ARCH_KGDB後,qemu也能進行調試。狀況不變。看來qemu能徹底脫離內核中的kgdb就能調試內核。
步驟2: XXX@ubuntu:/new/myqemu/debian-x86$ qemu -hda debian.img -cdrom ../debian-testing-i386-CD-1.iso -m 500 -S -s 步驟3: 由下圖咱們注意到:「基於qemu和qemu內置gdbstub」這個方法的調試,最先只能從函數 start_kernel () 開始進行。 內核在start_kernel ()以前的初始化過程就沒法觀察了。這就是這個方法的最大缺點。但下節利用skyeye調試arm-linux的 方法就能夠從第一個機器指令開始進行。 XXX@ubuntu:/storage/myqemu/new/linux-2.6.26$ gdb ./vmlinux GNU gdb 6.8-debian Copyright (C) 2008 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> 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 "i486-linux-gnu"... 0x0000fff0 in ?? () Breakpoint 1 at 0xc037f5ca: file init/main.c, line 535. (gdb) c Continuing. Breakpoint 1, start_kernel () at init/main.c:535 535 { (gdb) 調試示意圖: 給sys_read下斷點 (gdb) b sys_read Breakpoint 2 at 0xc017585e: file fs/read_write.c, line 360. (gdb) 用快捷鍵 ctrl+x+2 打開tui,並按c繼續運行,然後攔截到sys_read ┌──fs/read_write.c────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │354 { │ │355 struct file *file; │ │356 ssize_t ret = -EBADF; │ │357 int fput_needed; │ │358 │ │359 file = fget_light(fd, &fput_needed); │ B+>│360 if (file) { │ │361 loff_t pos = file_pos_read(file); │ │362 ret = vfs_read(file, buf, count, &pos); │ │363 file_pos_write(file, pos); │ │364 fput_light(file, fput_needed); │ │365 } │ │366 │ │367 return ret; │ ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │0xc017585a <sys_read> push %ebp │ │0xc017585b <sys_read+1> mov %esp,%ebp │ │0xc017585d <sys_read+3> push %esi │ B+>│0xc017585e <sys_read+4> mov $0xfffffff7,%esi │ │0xc0175863 <sys_read+9> push %ebx │ │0xc0175864 <sys_read+10> sub $0xc,%esp │ │0xc0175867 <sys_read+13> mov 0x8(%ebp),%eax │ │0xc017586a <sys_read+16> lea -0xc(%ebp),%edx │ │0xc017586d <sys_read+19> call 0xc0175f65 <fget_light> │ │0xc0175872 <sys_read+24> test %eax,%eax │ │0xc0175874 <sys_read+26> mov %eax,%ebx │ │0xc0175876 <sys_read+28> je 0xc01758b1 <sys_read+87> │ │0xc0175878 <sys_read+30> mov 0x24(%ebx),%edx │ │0xc017587b <sys_read+33> mov 0x20(%eax),%eax │ └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ remote Thread 42000 In: sys_read Line: 360 PC: 0xc017585e (gdb) c Continuing. Breakpoint 2, sys_read (fd=3, buf=0xbfc781a4 "", count=512) at fs/read_write.c:360 (gdb)
目標:
本節在qemu虛擬機上安裝一個基於arm的「桌面「系統,能夠有X桌面,該虛擬系統能利用apt-get從debian的軟件庫下載數不完的用交叉編譯已經編譯好的arm下的程序和工具。除了虛擬處理器是arm外,簡直就是PC機。能夠進行應用程序的本機(在虛擬機內進行)調試。可是,本人裝的時候,若是選了安裝桌面環境,內核就啓動失敗,好像是提示文件系統出錯。[成功的麻煩把過程貼出來]
過程是:
Debian on an emulated ARM machine http://www.aurel32.net/info/debian_arm_qemu.php
下面是過程的提煉步驟,方便查看。
1.建立虛擬硬盤
$ qemu-img create -f qcow hda.img 40G
2.下載必要文件
$ wget http://people.debian.org/~aurel32/arm-versatile/vmlinuz-2.6.18-6-versatile $ wget http://people.debian.org/~aurel32/arm-versatile/initrd.img-2.6.18-6-versatile $ wget http://ftp.de.debian.org/debian/dists/etch/main/installer-arm/current/images/rpc/netboot/initrd.gz
2.安裝系統
qemu-system-arm -M versatilepb -kernel vmlinuz-2.6.18-6-versatile -initrd initrd.gz -hda hda.img -append "root=/dev/ram" 在安裝過程當中,爲了節省時間,在這步choose a mirror of the debian archive 選http 回車 ; debian archive mirror country 選taiwan 回車; debian archive mirror 選ftp.tw.debian.org 安裝好基本系統後,不要選擇安裝Desktop environment 安裝完成後,它提示你把光盤拿掉並重啓系統時,終止掉qemu。並用下一步的命令啓動qemu.不要回車,不然又從新安裝。
3. 第一次啓動系統
$ qemu-system-arm -M versatilepb -kernel vmlinuz-2.6.18-6-versatile -initrd initrd.img-2.6.18-6-versatile -hda hda.img -append "root=/dev/sda1"
4. 把舊的內核,intrd.img製做工具安裝到虛擬機的系統內(操做在虛擬機內)
$ apt-get install initramfs-tools $ wget http://people.debian.org/~aurel32/arm-versatile/linux-image-2.6.18-6-versatile_2.6.18.dfsg.1-18etch1+versatile_arm.deb $ su -c "dpkg -i linux-image-2.6.18-6-versatile_2.6.18.dfsg.1-18etch1+versatile_arm.deb"
5.其餘更多的玩法請看原文http://www.aurel32.net/info/debian_arm_qemu.php
參考:
Debian ARM Linux on Qemu
http://909ers.apl.washington.edu/~dushaw/ARM/#SYSTEM
Running Linux for ARM processors under QEMU
http://iomem.com/index.php?archives/2-Running-Linux-for-ARM-processors-under-QEMU.html&serendipity[entrypage]=2
Debian on an emulated ARM machine
[暫時無法子,期待擴展。下面這個例子能夠,但沒嘗試。估計這個方法與下節的利用skyeye的方法相比,沒有優點。由於這個方法可能也是不能進行全程調試。可是下面網站的資料仍是有必定參考價值的。]
使用qemu-jk2410作為學習環境:
另外:看看下面這個站點,
Firmware Linux: http://landley.net/code/firmware/
相對於利用qemu的方式,用skyeye虛擬機調試內核有個很重要的
優勢是:
調試能夠從第一條機器指令開始。這對研究系統啓動過程提供了極大的便利。
該文很是好,好像沒啥要擴充的
SkyEye硬件模擬平臺,第二部分: 安裝與使用
http://www.ibm.com/developerworks/cn/linux/l-skyeye/part2/
SkyEye User Manual http://www.skyeye.org/wiki/UserManual
http://skyeye.wiki.sourceforge.net/
參考文檔:
Linux-2.6.20 on XXX platform
http://skyeye.wiki.sourceforge.net/Linux
uClinux-dist-20070130 on XXX platform
http://skyeye.wiki.sourceforge.net/uClinux
http://www.linuxfans.org/bbs/thread-182101-1-1.html
安裝:
1. 安裝主程序
在ubuntu系統能進行在線安裝,但版本是v1.2,不是最新的
$sudo apt-get install skyeye
2. 測試套件
測試套件下載後解壓開便可
地址:http://sourceforge.net/project/showfiles.php?group_id=85554
目的:
儘量快的成功運行一個arm linux虛擬機。若是您化了很長時間也沒法編譯出一個能運行的內核,或寫不出一個恰當的skyeye.conf時,在你的熱情受到打擊以前,我想這節是你急需的。
操做步驟:
1.依照上節說明安裝好主程序,下載並解壓好測試套件
2.進入測試套件的目錄 skyeye-testsuite-1.2.5/linux/s3c2410/s3c2410x-2.6.14
能夠看到有三個文件initrd.img skyeye.conf vmlinux
3.運行虛擬機
$skyeye -e vmlinux
注意下面的提示,說明平時要注意在啓動命令前加上sudo
NOTICE: you should be root at first !!! NOTICE: you should inmod linux kernel net driver tun.o!!! NOTICE: if you don't make device node, you should do commands: NOTICE: mkdir /dev/net; mknod /dev/net/tun c 10 200 NOTICE: now the net simulation function can not support!!! NOTICE: Please read SkyEye.README and try again!!!
4.能夠看到,一個2.6.14 版本的linux跑起來了,還帶有一個lcd.
參考:
http://skyeye.wiki.sourceforge.net/linux_2_6_17_lubbock
環境條件:
1. ubuntu hardy 8.04
2. 安裝了debian提供的交叉編譯工具套件 arm-linux-gnueabi- (4.2版本)
目標:
這小節能獲得基於pxa平臺(相似s3c2410,也基於arm核心)的linux2.6.20內核的虛擬系統,具有調試功能。相比「基於qemu和qemu內置gdbstub」該節,利用skyeye的調試有那節所沒有的優勢:調試時能夠從內核運行的第一條指令開始[這就是模擬硬件調試?]。
參考手冊:
XScale PXA250開發手冊 http://soft.laogu.com/download/intelpxa250.pdf
ARMv5 體系結構參考手冊 http://www.arm.com/community/university/eulaarmarm.html
操做步驟:
1. 下載linux-2.6.20 (因爲交叉編譯器太新,若是利用linux-2.6.17則編譯不過)
2. 修改文件include/asm-arm/arch-pxa/memory.h 第18行
#define PHYS_OFFSET UL(0xa0000000) 爲 #define PHYS_OFFSET UL(0xc0000000)
3. 下載內核配置選項,放置於linux-2.6.20源碼的根目錄下http://skyeye.wiki.sourceforge.net/space/showimage/skyeye_2.6.17_lubbock.config
這個下載好的配置文件已經幫咱們作了的兩件事
首先,在block device菜單下配置了ramdisk和initrd的支持
其次,把內核原來的啓動參數改成
root=/dev/ram0 console=ttyS0 initrd=0xc0800000,0x00800000 rw mem=64M
4. 把下載到的skyeye_2.6.17_lubbock.config改名爲.config
5. 編譯內核
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
6. 建立文件 skyeye.conf,內容以下:
cpu: pxa25x mach: pxa_lubbock mem_bank: map=I, type=RW, addr=0x40000000, size=0x0c000000 mem_bank: map=M, type=RW, addr=0xc0000000, size=0x00800000 mem_bank: map=M, type=RW, addr=0xc0800000, size=0x00800000, file=./initrd.img mem_bank: map=M, type=RW, addr=0xc1000000, size=0x00800000 mem_bank: map=M, type=RW, addr=0xc1800000, size=0x02800000
7. 從skyeye的測試套件中拷貝initrd.img到linux-2.6.20源碼根目錄下。該initrd.img的路徑是:
skyeye-testsuite-1.2.5/linux/pxa/2.6.x/
8. 運行內核看看,在linux-2.6.20源碼根目錄下運行下面的命令。能夠看到,內核成功運行
sudo skyeye -e vmlinux
調試:
1. 在linux-2.6.20源碼根目錄下運行命令:
sudo skyeye -d -e vmlinux
2. 在源碼根目錄下新開一個終端,並運行:
arm-linux-gnueabi-gdb ./vmlinux
gdb界面出來後
(gdb) target remote:12345
以後能夠看到,下斷點,查看彙編等一切調試功能和x86下都同樣。
3. ddd下如何調用arm-linux-gnueabi-gdb ? 答
$ ddd --debugger arm-linux-gnueabi-gdb ./vmlinux
[啓動過程當中有若干錯誤提示,但內核能啓動成功並運行。有待研究]
目標:
獲得一個基於s3c2410cpu的2.6.26最新穩定內核的虛擬系統,能進行全程的內核調試,即調試能從第一條機器指令開始進行。
參考:
http://skyeye.wiki.sourceforge.net/Linux
http://www.linuxfans.org/bbs/thread-182101-1-1.html
環境條件:
1. ubuntu hardy 8.04
2. 安裝了debian提供的交叉編譯工具套件 arm-linux-gnueabi- (4.2版本)
操做步驟:
1.依據「安裝交叉編譯工具」這節,安裝好交叉編譯工具
2.修改源碼
將include/asm-arm/arch-s3c2410/map.h裏的 #define S3C2410_CS6 (0x30000000) 改成 #define S3C2410_CS6 (0xc0000000) 將include/asm-arm/arch-s3c2410/memory.h裏的 #define PHYS_OFFSET UL(0x30000000) 改成 #define PHYS_OFFSET UL(0xc0000000)
3.把默認.config替換爲s3c2410版本
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- s3c2410_defconfig
3.修改配置文件
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- menuconfig 進入[Device Driver] ->[ Character Driver] -> [Serial Driver] 等菜單下 , 取消8250/16550 and compatible serial support的選擇
4.修改內核啓動命令
在Boot option --> Default kernel command string 裏輸入 mem=32M console=ttySAC0 root=/dev/ram initrd=0xc0800000,0x00800000 ramdisk_size=2048 rw
5.編譯
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
6.從skyeye的測試套件中拷貝相應的文件initrd.img和skyeye.conf到linux-2.6.26源碼根目錄下。這兩個文件的位於
skyeye-testsuite-1.25/linux/s3c2410/s3c2410x-2.6.14/中
7.啓動虛擬機
XXX@ubuntu:~/dt/linux-2.6.26$ sudo skyeye -e vmlinux
8.啓動完成後那激動人心的logo以下
Welcome to _ _____ __ __ _ _ / / / __ / / /_/ / | | |_| / _ / | | | | / // // / | | _ ____ _ _ _ _ / /_/ / | |__| | / / /_/ / /| | | | _ /| | | |/ // / / /___/ / | |__/ / | | | || |___ | | |_| | |_| |/ / /_/ /_/| | /_/|_| |_||_____||_|_| |_|/____|/_//_/ ARMLinux for Skyeye For further information please check: http://www.skyeye.org/ BusyBox v1.4.1 (2007-02-10 01:19:06 CST) Built-in shell (ash) Enter 'help' for a list of built-in commands. /bin/ash: can't access tty; job control turned off / $ uname -a Linux skyeye 2.6.26 #2 Sun Oct 5 19:56:57 CST 2008 armv4tl unknown / $
調試:
1. 在linux-2.6.26源碼根目錄下新建文件」.gdbinit」,內容是:
(gdb) target remote:12345
2. 在linux-2.6.26源碼根目錄下命令:
sudo skyeye -d -e vmlinux
3. 在源碼根目錄下新開一個終端,並運行:
arm-linux-gnueabi-gdb ./vmlinux
以後能夠看到,下斷點,查看彙編等一切調試功能和x86下都同樣。
4. ddd下如何調用arm-linux-gnueabi-gdb ? 答
$ ddd --debugger arm-linux-gnueabi-gdb ./vmlinux
截圖:
步驟2: XXX@ubuntu:~/桌面/test/linux-2.6.26_s3c2410$ sudo skyeye -d -e vmlinux big_endian is false. arch: arm cpu info: armv4, arm920t, 41009200, ff00fff0, 2 mach info: name s3c2410x, mach_init addr 0x805f030 lcd_mod:1 dbct info: Note: DBCT not compiled in. This option will be ignored uart_mod:0, desc_in:, desc_out:, converter: SKYEYE: use arm920t mmu ops Loaded RAM ./initrd.img start addr is set to 0xc0008000 by exec file. debugmode= 1, filename = skyeye.conf, server TCP port is 12345 ------------------------ 步驟3: fqh@ubuntu:~/桌面/test/linux-2.6.26_s3c2410$ arm-linux-gnueabi-gdb vmlinux GNU gdb 6.8-debian Copyright (C) 2008 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> 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 "--host=i486-linux-gnu --target=arm-linux-gnueabi"... stext () at arch/arm/kernel/head.S:80 80 msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode Current language: auto; currently asm (gdb) source extendinstr //載入輔助的gdb宏 -------------- 用快捷鍵 ctrl+x+2 打開tui模式後的圖示,可看到調試是從第一條指令開始的。這對研究系統啓動過程提供了極大的便利。 ┌──arch/arm/kernel/head.S────────────────────────────────────────────────────────────────────────────┐ >│80 msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode │ │81 @ and irqs disabled │ │82 mrc p15, 0, r9, c0, c0 @ get processor id │ │83 bl __lookup_processor_type @ r5=procinfo r9=cpuid │ │84 movs r10, r5 @ invalid processor (r5=0)? │ │85 beq __error_p @ yes, error 'p' │ │86 bl __lookup_machine_type @ r5=machinfo │ │87 movs r8, r5 @ invalid machine (r5=0)? │ │88 beq __error_a @ yes, error 'a' │ │89 bl __vet_atags │ │90 bl __create_page_tables │ └────────────────────────────────────────────────────────────────────────────────────────────────────┘ >│0xc0008000 <stext> msr CPSR_c, #211 ; 0xd3 │ │0xc0008004 <stext+4> mrc 15, 0, r9, cr0, cr0, {0} │ │0xc0008008 <stext+8> bl 0xc00082f8 <__lookup_processor_type> │ │0xc000800c <stext+12> movs r10, r5 │ │0xc0008010 <stext+16> beq 0xc0008190 <__error_p> │ │0xc0008014 <stext+20> bl 0xc0008358 <__lookup_machine_type> │ │0xc0008018 <stext+24> movs r8, r5 │ │0xc000801c <stext+28> beq 0xc00081e8 <__error_a> │ │0xc0008020 <stext+32> bl 0xc00083a0 <__vet_atags> │ │0xc0008024 <stext+36> bl 0xc0008078 <__create_page_tables> │ │0xc0008028 <stext+40> ldr sp, [pc, #240] ; 0xc0008120 <__switch_data> │ └────────────────────────────────────────────────────────────────────────────────────────────────────┘ remote Thread 42000 In: stext Line: 80 PC: 0xc0008000 (gdb) b sys_read //下斷點 Breakpoint 1 at 0xc008cc4c: file fs/read_write.c, line 354. (gdb) c ---------------- 調試示意圖 效果可能與你機器上看到的不同。這個例子中,每一個gdb單步指令都會自動顯示backtrace。這是由於本人使用了章節「gdb宏」中的extendinstr宏。 ┌──include/asm/thread_info.h──────────────────────────────────────────────────────────────────────────────────────────────┐ │91 */ │ │92 static inline struct thread_info *current_thread_info(void) __attribute_const__; │ │93 │ │94 static inline struct thread_info *current_thread_info(void) │ │95 { │ │96 register unsigned long sp asm ("sp"); │ >│97 return (struct thread_info *)(sp & ~(THREAD_SIZE - 1)); │ │98 } │ │99 │ │100 /* thread information allocation */ │ │101 #ifdef CONFIG_DEBUG_STACK_USAGE │ │102 #define alloc_thread_info(tsk) / │ │103 ((struct thread_info *)__get_free_pages(GFP_KERNEL | __GFP_ZERO, / │ │104 THREAD_SIZE_ORDER)) │ └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ │0xc008d480 <fget_light> mov r12, sp │ │0xc008d484 <fget_light+4> push {r11, r12, lr, pc} │ │0xc008d488 <fget_light+8> sub r11, r12, #4 ; 0x4 │ │0xc008d48c <fget_light+12> bic r3, sp, #8128 ; 0x1fc0 │ >│0xc008d490 <fget_light+16> bic r3, r3, #63 ; 0x3f │ │0xc008d494 <fget_light+20> ldr r3, [r3, #12] │ │0xc008d498 <fget_light+24> mov r12, #0 ; 0x0 │ │0xc008d49c <fget_light+28> ldr r2, [r3, #560] │ │0xc008d4a0 <fget_light+32> str r12, [r1] │ │0xc008d4a4 <fget_light+36> ldr r3, [r2] │ │0xc008d4a8 <fget_light+40> cmp r3, #1 ; 0x1 │ │0xc008d4ac <fget_light+44> bne 0xc008d4d0 <fget_light+80> │ │0xc008d4b0 <fget_light+48> ldr r2, [r2, #4] │ │0xc008d4b4 <fget_light+52> ldr r3, [r2] │ └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ remote Thread 42000 In: fget_light Line: 97 PC: 0xc008d490 Program received signal SIGHUP, Hangup. 0xc008d490 in fget_light (fd=1, fput_needed=0xc1c17ed4) at include/asm/thread_info.h:97 ------------------- #0 0xc008d490 in fget_light (fd=1, fput_needed=0xc1c17ed4) at include/asm/thread_info.h:97 #1 0xc008cc5c in sys_read (fd=1, buf=0xc1196800 "", count=512) at fs/read_write.c:359 #2 0xc000ac7c in rd_load_image (from=0xc02b43bc "/initrd.image") at init/do_mounts_rd.c:108 #3 0xc000bbe8 in initrd_load () at init/do_mounts_initrd.c:121 #4 0xc00094c0 in prepare_namespace () at init/do_mounts.c:384 #5 0xc0008a9c in kernel_init (unused=<value optimized out>) at init/main.c:878 #6 0xc0048484 in sys_waitid (which=<value optimized out>, upid=-1044283692, infop=0x0, options=0, ru=Cannot access memory at address 0x4 ) at kernel/exit.c:1689 Backtrace stopped: previous frame inner to this frame (corrupt stack?) (gdb)
1. 新版本的改進
在ubuntu下利用在線安裝命令所安裝的skyeye是舊的版本,新版本修正了舊版本的一些小問題。好比,舊版本在調試時會出現下面一些煩人的小提示。
Can't send signals to this remote system. SIGHUP not sent. Program received signal SIGHUP, Hangup.
可是,兩個版本並非徹底兼容的,主要是skyeye.conf的處理上。不過,幸虧這些都是很容易解決的問題。
2. 新版本的安裝
http://sourceforge.net/project/showfiles.php?group_id=85554
到上面的網站下載最新版本,目前是skyeye-1.2.6_rc1。解壓後用下面命令編譯就能夠了
$./configure $ make STATIC=1
而後把在源碼根目錄下生成的skyeye拷到內核目錄下運行便可。這樣系統中的老版本skyeye還照樣能夠使用。
sudo ./skyeye -d -e vmlinux
3. 新老版本的兼容問題
主要是skyeye.conf的格式識別上。老版本要求load_address,load_address_mask不能寫在skyeye.conf文件內部,只能用-l選項指定。若是運行老版本時提示skyeye.conf出錯,你就得去查查那裏,並手動修改處理一下便可。
爲qq2440平臺移植2.6.26或更新內核,並創建kgdb調試環境
進行中...
[移植中的一些零碎的筆記]
1.內核版本
使用linus的git,可是已知2.6.25中arm已經支持kgdb了。
XXX@ubuntu:/storage/linus-git/linux-2.6$ git-describe v2.6.27-rc9-2-g85ba94b
2.
arm體系的默認配置文件在 arch/arm/configs make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- s3c2410_defconfig make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- menuconfig 選取如下選現 CONFIG_DEBUG_INFO=y CONFIG_KGDB=y CONFIG_KGDB_SERIAL_CONSOLE=y make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- 移植環境 windows:硬盤安裝的真實系統(XP) ubuntu: 運行在windows下的vmware虛擬機中 qq2440開發板:真實開發板,IP是192.168.1.230 第一天:(完成) 熟悉開發板,PC機,虛擬機的網絡互連 理解內核啓動過程 開發板與PC機(XP)PING不通的緣由有 1. PC機開着防火 2. PC機上的VMWARE的網絡設置有問題(先卸載確認) 3. 安全類軟件形成,好比卡巴司機(先卸載,不行重裝系統) ubuntu的網絡配置分兩種狀況,一種是平時上網用的,一種是和開發板通信用的。 平時使用虛擬機ubuntu上網的配置: 鏈接方式選出NAT: used to share the host's IP address 虛擬系統啓動後,桌面右上角的 wired connection->properties->configuration選automatic configuration(DHCP) 開發板掛載ubuntu虛擬系統中的nfs 1.虛擬機自己的網絡設置不用動 2.虛擬系統如ubuntu的網卡設置改成橋接 edit virtual machine settings->virtual machine setting->hardware->ethernet ->bridged:connected directly to the physical network 3.虛擬系統啓動後,桌面右上角的manual network configuration要改. 點左鍵->network settings->wired connection->properties:enable roaming mode不選, connection settings configuration:static IP address IP address:192.168.1.111 與PC機IP,開發板IP同個網段 subnet mask:255.255.255.0 gateway address:空 PC機網絡信息: Ethernet adapter 本地鏈接: Connection-specific DNS Suffix . : IP Address. . . . . . . . . . . . : 192.168.1.100 Subnet Mask . . . . . . . . . . . : 255.255.255.0 Default Gateway . . . . . . . . . : 開發板的網絡信息: [root@(none) /]# ifconfig eth0 Link encap:Ethernet HWaddr 08:00:3E:26:0A:5B inet addr:192.168.1.230 Bcast:192.168.1.255 Mask:255.255.255.0 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:1011 errors:0 dropped:0 overruns:0 frame:0 TX packets:610 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:111858 (109.2 KiB) TX bytes:57276 (55.9 KiB) Interrupt:53 Base address:0x300 windows打開ubuntu中的samba共享目錄的方法 //192.168.1.111 ubuntu中nfs服務的安裝和啓用 $ sudo apt-get install nfs-common $ sudo apt-get install nfs-kernel-server $ sudo vi /etc/exports /new/root_nfs *(rw,sync) $ sudo /etc/init.d/nfs-kernel-server start 4. 檢查 $ showmount -e localhost 開發板掛載nfs成功後可看到顯示結果是 All mount points on localhost: 192.168.1.230:/new/root_nfs 開發板掛載ubuntu中的nfs (此時運行的文件系統仍是在開發板上) mount -t nfs -o nolock 192.168.1.111:/new/root_nfs /tmp/fuck 192.168.1.111:ubuntu的IP /tmp/fuck:開發板中的掛載點 [root@(none) /]# mount -t nfs -o nolock 192.168.1.111:/new/root_nfs /tmp/fuck [root@(none) /]# cd /tmp/fuck/ [root@(none) fuck]# ls bin lib proc usr dev linuxrc sbin var etc mnt shanghaitan.mp3 www home opt tmp ----- 經過nfs啓動開發板 (掛載的文件系統是在ubuntu虛擬系統上) 下面文字來自於:Embedded Linux Primer: A Practical, Real-World Approach ip=192.168.1.139:192.168.1.1:192.168.1.1:255.255.255.0:coyote1:eth0:off ip=<client-ip>:<server-ip>:<gw-ip>:<netmask>:<hostname>:<device>:<PROTO> Here, client-ip is the target's IP address; server-ip is the address of the NFS server; gw-ip is the gateway (router), in case the server-ip is on a different subnet; and netmask defines the class of IP addressing. hostname is a string that is passed as the target hostname; device is the Linux device name, such as eth0; and PROTO defines the protocol used to obtain initial IP parameters. 本人的實際操做的命令參數是: param set linux_cmd_line "console=ttySAC0 root=/dev/nfs nfsroot=192.168.1.111:/new/root_nfs ip=192.168.1.130:192.168.1.111:192.168.1.111:255.255.255.0:sbc2440.arm9.net:eth0:off" 注意把編輯器的換行功能去掉後,再複製上面的命令。 192.168.1.130是開發板的IP,系統啓動後,用ifconfig就會顯示這個IP地址。能夠隨意設置,固然要知足和PC機,ubuntu的IP在同個網段,並且不能衝突的先前條件。 130:192.168.1.111:nfs的server,也就是ubuntu的IP 按住空格健重啓開發板,出現: +---------------------------------------------+ | S3C2440A USB Downloader ver R0.03 2004 Jan | +---------------------------------------------+ USB: IN_ENDPOINT:1 OUT_ENDPOINT:3 FORMAT: <ADDR(DATA):4>+<SIZE(n+10):4>+<DATA:n>+<CS:2> NOTE: Power off/on or press the reset button for 1 sec in order to get a valid USB device address. NAND device: Manufacture ID: 0xec, Chip ID: 0x76 (Samsung K9D1208V0M) Found saved vivi parameters. Press Return to start the LINUX/Wince now, any other key for vivi type "help" for help. Supervivi> menu ##### FriendlyARM BIOS for 2440 ##### [x] bon part 0 320k 2368k [v] Download vivi [k] Download linux kernel [y] Download root_yaffs image [c] Download root_cramfs image [n] Download Nboot [e] Download Eboot [i] Download WinCE NK.nb0 [w] Download WinCE NK.bin [d] Download & Run [f] Format the nand flash [p] Partition for Linux [b] Boot the system [s] Set the boot parameters [t] Print the TOC struct of wince [q] Goto shell of vivi Enter your selection: s //<-- ##### Parameter Menu ##### [r] Reset parameter table to default table [s] Set parameter [v] View the parameter table [w] Write the parameter table to flash memeory [q] Quit Enter your selection: s //<-- Enter the parameter's name(mach_type, media_type, linux_cmd_line, etc): linux_cmd_line Enter the parameter's value(if the value contains space, enclose it with "): "console=ttySAC0 root=/dev/nfs nfsroot=192.168.1.111:/new/root_nfs ip=192.168.1.130:192.168.1.111:192.168.1.111:255.255.255.0:sbc2440.arm9.net:eth0:off" Change linux command line to "console=ttySAC0 root=/dev/nfs nfsroot=192.168.1.111:/new/root_nfs ip=192.168.1.130:192.168.1.111:192.168.1.111:255.255.255.0:sbc2440.arm9.net:eth0:off" ##### Parameter Menu ##### [r] Reset parameter table to default table [s] Set parameter [v] View the parameter table [w] Write the parameter table to flash memeory [q] Quit Enter your selection: w //<-- Found block size = 0x0000c000 Erasing... ... done Writing... ... done Written 49152 bytes Saved vivi private data 次日:(完成) 文件系統製做 理解系統啓動過程 先實驗在skyeye下能不能成功,學習一下文件系統的製做。然後再下載到開發板實驗 dd if=/dev/zero of=./test.image bs=1k count=8192 塊大小單位:1k,8120塊,8M mke2fs ./test.image 格式化 mkdir fuckroot tar -xzvf root_mini.tgz sudo mount -o loop test.image ./fuckroot/ cp -r root_mini/* fuckroot/ sudo umount fuckroot/ 能夠將文件系統映像壓縮後再使用: gzip -v9 test.image > test.image.gz 本人這個文件系統解壓後的大小是6.4M,製做成8M大的test.image,壓縮成test.image.gz後只有2.9M大。 可是利用skyeye啓動時,解壓花的時間比較長。 命令行中的ramdisk_size過小,修改. mem=32M console=ttySAC0 root=/dev/ram initrd=0xc0800000,0x00800000 ramdisk_size=8192 rw initcall_debug ramdisk_size=N This parameter tells the RAM disk driver to set up RAM disks of N k size. 問題,文件系統沒建立console設備節點: RAMDISK: Loading 8192KiB [1 disk] into ram disk... done. VFS: Mounted root (ext2 filesystem). Freeing init memory: 132K Warning: unable to open an initial console. 建立rootfs過程當中,在/dev目錄下手動建立以下節點: mknod -m 660 null c 1 3 mknod -m 660 console c 5 1 結果: VFS: Mounted root (ext2 filesystem). Freeing init memory: 132K hwclock: Could not access RTC: No such file or directory mknod: /dev/pts/0: No such file or directory mount: Mounting none on /tmp failed: Invalid argument mount: Mounting none on /var failed: Invalid argument /etc/init.d/rcS: /etc/init.d/rcS: 44: cannot create /dev/vc/0: Directory nonexistent /etc/init.d/rcS: /etc/init.d/rcS: 45: cannot create /dev/vc/0: Directory nonexistent /etc/rc.d/init.d/httpd: /etc/rc.d/init.d/httpd: 16: /sbin/boa: not found /etc/init.d/rcS: /etc/init.d/rcS: 48: cannot create /dev/vc/0: Directory nonexistent /etc/init.d/rcS: /etc/init.d/rcS: 49: cannot create /dev/vc/0: Directory nonexistent /etc/rc.d/init.d/leds: /etc/rc.d/init.d/leds: 16: /etc/init.d/rcS: /etc/init.d/rcS: 52: cannot create /dev/vc/0: Directory nonexistent /etc/init.d/rcS: /etc/init.d/rcS: 53: cannot create /dev/vc/0: Directory nonexistent /sbin/led-player: not found SIOCSIFADDR: No such device SIOCGIFFLAGS: No such device /etc/init.d/rcS: /etc/init.d/rcS: 59: /sbin/madplay: not found Please press Enter to activate this console. -sh: can't access tty; job control turned off id: unknown uid 0 [@FriendlyARM /]# ls bin home lost+found sbin var dev lib mnt tmp www etc linuxrc proc usr [@FriendlyARM /dev]# ls console dsp fb0 mixer null sda1 tty1 video0 還有一堆提示,但總算系統能跑了。 如今個人心頭大患是udev的問題,由於2.6.26內核中沒有devfs了。但有下面這篇文章參考 udev輕鬆上路 http://www.linuxforum.net/forum/showflat.php?Cat=&Board=embedded&Number=628054&page=0&view=collapsed&sb=5&o=0&fpart= 第三天:(完成) 移植內核2.6.27-rc9到qq2440開發板,實現基本功能,能掛載板上文件系統. 步驟: 1.使用vivi修改mach_type參數 2.修改時鐘頻率 3.修改源碼正確分區 4.禁止nand的ECC校驗 分述: 問題1.表現 Uncompressing Linux................................................................................................................. done, booting the kernel. Error: unrecognized/unsupported machine ID (r1 = 0x0000030e). Available machine support: ID (hex) NAME 000000c1 SMDK2410 0000015b IPAQ-H1940 0000039f Acer-N35 00000290 Acer-N30 0000014b Simtec-BAST 000002a8 Nex Vision - Otom 1.1 00000400 AML_M5900 000001db Thorcom-VR1000 00000454 QT2410 000003fe SMDK2413 000003f1 SMDK2412 00000377 S3C2413 00000474 VSTMS 000002de Simtec-Anubis 0000034a Simtec-OSIRIS 00000250 IPAQ-RX3715 0000016a SMDK2440 000002a9 NexVision - Nexcoder 2440 0000043c SMDK2443 Please check your kernel config and/or bootloader. 解決方法: ##### Parameter Menu ##### [r] Reset parameter table to default table [s] Set parameter [v] View the parameter table [w] Write the parameter table to flash memeory [q] Quit Enter your selection: s Enter the parameter's name(mach_type, media_type, linux_cmd_line, etc): mach_type Enter the parameter's value(if the value contains space, enclose it with "): 362 //<--- Change 'mach_type' value. 0x0000030e(782) to 0x0000016a(362) 問題2.表現 Uncompressing Linux................................................................................................................. done, booting the kernel. 8?'·{e#???;?·7'0??3G?#?G'?亂碼 解決方法: static void __init smdk2440_map_io(void) { s3c24xx_init_io(smdk2440_iodesc, ARRAY_SIZE(smdk2440_iodesc)); s3c24xx_init_clocks(12000000);//修改處,原爲16934400 s3c24xx_init_uarts(smdk2440_uartcfgs, ARRAY_SIZE(smdk2440_uartcfgs)); } 問題3.表現 VFS: Cannot open root device "mtdblock2" or unknown-block(31,2) Please append a correct "root=" boot option; here are the available partitions: 1f00 16 mtdblock0 (driver?) 1f01 2048 mtdblock1 (driver?) 1f02 4096 mtdblock2 (driver?) 1f03 2048 mtdblock3 (driver?) 1f04 4096 mtdblock4 (driver?) 1f05 10240 mtdblock5 (driver?) 1f06 24576 mtdblock6 (driver?) 1f07 16384 mtdblock7 (driver?) Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(31,2) 解決: 依據nand分區修改源碼: static struct mtd_partition smdk_default_nand_part[] = { [0] = { .name = "vivi", .size = 0x00030000, .offset = 0, }, [1] = { .name = "kernel", .offset = 0x00050000, .size = 0x00200000, }, [2] = { .name = "root", .offset = 0x00250000, .size = 0x03dac000, }, }; 問題4.表現 Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(31,2) 致使上面panic的緣由是沒有禁止Flash ECC校驗 解決: s3c2410_nand_init_chip() .. if (set->disable_ecc) chip->ecc.mode = NAND_ECC_NONE; chip->ecc.mode = NAND_ECC_NONE;//<-在函數最後加上 啓動信息: Copy linux kernel from 0x00050000 to 0x30008000, size = 0x00200000 ... done zImage magic = 0x016f2818 Setup linux parameters at 0x30000100 linux command line is: "noinitrd root=/dev/mtdblock2 init=/linuxrc console=ttySAC0" MACH_TYPE = 362 NOW, Booting Linux...... Uncompressing Linux................................................................................................................. done, booting the kernel. Linux version 2.6.27-rc9 (fqh@ubuntu-sniper) (gcc version 4.2.4 (Debian 4.2.4-3)) #8 Sat Oct 11 03:17:21 CST 2008 CPU: ARM920T [41129200] revision 0 (ARMv4T), cr=c0007177 Machine: SMDK2440 ATAG_INITRD is deprecated; please update your bootloader. Memory policy: ECC disabled, Data cache writeback CPU S3C2440A (id 0x32440001) S3C244X: core 405.000 MHz, memory 101.250 MHz, peripheral 50.625 MHz S3C24XX Clocks, (c) 2004 Simtec Electronics CLOCK: Slow mode (1.500 MHz), fast, MPLL on, UPLL on CPU0: D VIVT write-back cache CPU0: I cache: 16384 bytes, associativity 64, 32 byte lines, 8 sets CPU0: D cache: 16384 bytes, associativity 64, 32 byte lines, 8 sets Built 1 zonelists in Zone order, mobility grouping on. Total pages: 16256 Kernel command line: noinitrd root=/dev/mtdblock2 init=/linuxrc console=ttySAC0 irq: clearing pending ext status 00000200 irq: clearing subpending status 00000002 PID hash table entries: 256 (order: 8, 1024 bytes) timer tcon=00000000, tcnt a4ca, tcfg 00000200,00000000, usec 00001e57 Console: colour dummy device 80x30 console [ttySAC0] enabled Dentry cache hash table entries: 8192 (order: 3, 32768 bytes) Inode-cache hash table entries: 4096 (order: 2, 16384 bytes) Memory: 64MB = 64MB total Memory: 61140KB available (3224K code, 335K data, 144K init) Calibrating delay loop... 201.93 BogoMIPS (lpj=504832) Mount-cache hash table entries: 512 CPU: Testing write buffer coherency: ok net_namespace: 440 bytes NET: Registered protocol family 16 S3C2410 Power Management, (c) 2004 Simtec Electronics S3C2440: Initialising architecture S3C2440: IRQ Support S3C24XX DMA Driver, (c) 2003-2004,2006 Simtec Electronics DMA channel 0 at c4800000, irq 33 DMA channel 1 at c4800040, irq 34 DMA channel 2 at c4800080, irq 35 DMA channel 3 at c48000c0, irq 36 S3C244X: Clock Support, DVS off SCSI subsystem initialized usbcore: registered new interface driver usbfs usbcore: registered new interface driver hub usbcore: registered new device driver usb NET: Registered protocol family 2 IP route cache hash table entries: 1024 (order: 0, 4096 bytes) TCP established hash table entries: 2048 (order: 2, 16384 bytes) TCP bind hash table entries: 2048 (order: 1, 8192 bytes) TCP: Hash tables configured (established 2048 bind 2048) TCP reno registered NET: Registered protocol family 1 NetWinder Floating Point Emulator V0.97 (extended precision) JFFS2 version 2.2. (NAND) (SUMMARY) © 2001-2006 Red Hat, Inc. msgmni has been set to 119 io scheduler noop registered io scheduler anticipatory registered (default) io scheduler deadline registered io scheduler cfq registered Console: switching to colour frame buffer device 30x40 fb0: s3c2410fb frame buffer device lp: driver loaded but no devices found ppdev: user-space parallel port driver Serial: 8250/16550 driver4 ports, IRQ sharing enabled s3c2440-uart.0: s3c2410_serial0 at MMIO 0x50000000 (irq = 70) is a S3C2440 s3c2440-uart.1: s3c2410_serial1 at MMIO 0x50004000 (irq = 73) is a S3C2440 s3c2440-uart.2: s3c2410_serial2 at MMIO 0x50008000 (irq = 76) is a S3C2440 brd: module loaded loop: module loaded dm9000 Ethernet Driver, V1.31 Uniform Multi-Platform E-IDE driver Driver 'sd' needs updating - please use bus_type methods S3C24XX NAND Driver, (c) 2004 Simtec Electronics s3c2440-nand s3c2440-nand: Tacls=3, 29ns Twrph0=7 69ns, Twrph1=3 29ns NAND device: Manufacturer ID: 0xec, Chip ID: 0x76 (Samsung NAND 64MiB 3,3V 8-bit) NAND_ECC_NONE selected by board driver. This is not recommended !! Scanning device for bad blocks Bad eraseblock 562 at 0x008c8000 Bad eraseblock 566 at 0x008d8000 Creating 3 MTD partitions on "NAND 64MiB 3,3V 8-bit": 0x00000000-0x00030000 : "vivi" 0x00050000-0x00250000 : "kernel" 0x00250000-0x03ffc000 : "root" usbmon: debugfs is not available s3c2410-ohci s3c2410-ohci: S3C24XX OHCI s3c2410-ohci s3c2410-ohci: new USB bus registered, assigned bus number 1 s3c2410-ohci s3c2410-ohci: irq 42, io mem 0x49000000 usb usb1: configuration #1 chosen from 1 choice hub 1-0:1.0: USB hub found hub 1-0:1.0: 2 ports detected usbcore: registered new interface driver libusual usbcore: registered new interface driver usbserial usbserial: USB Serial support registered for generic usbcore: registered new interface driver usbserial_generic usbserial: USB Serial Driver core usbserial: USB Serial support registered for FTDI USB Serial Device usbcore: registered new interface driver ftdi_sio ftdi_sio: v1.4.3:USB FTDI Serial Converters Driver usbserial: USB Serial support registered for pl2303 usbcore: registered new interface driver pl2303 pl2303: Prolific PL2303 USB to serial adaptor driver mice: PS/2 mouse device common for all mice S3C24XX RTC, (c) 2004,2006 Simtec Electronics s3c2440-i2c s3c2440-i2c: slave address 0x10 s3c2440-i2c s3c2440-i2c: bus frequency set to 98 KHz s3c2440-i2c s3c2440-i2c: i2c-0: S3C I2C adapter S3C2410 Watchdog Timer, (c) 2004 Simtec Electronics s3c2410-wdt s3c2410-wdt: watchdog inactive, reset disabled, irq enabled TCP cubic registered NET: Registered protocol family 17 RPC: Registered udp transport module. RPC: Registered tcp transport module. drivers/rtc/hctosys.c: unable to open rtc device (rtc0) VFS: Mounted root (cramfs filesystem) readonly. Freeing init memory: 144K hwclock: Could not access RTC: No such file or directory mknod: /dev/pts/0: Read-only file system ln: /dev/video0: Read-only file system ln: /dev/fb0: Read-only file system ln: /dev/tty1: Read-only file system ln: /dev/dsp: Read-only file system ln: /dev/mixer: Read-only file system ln: /dev/sda1: Read-only file system /etc/init.d/rcS: /etc/init.d/rcS: 44: cannot create /dev/vc/0: Read-only file system /etc/init.d/rcS: /etc/init.d/rcS: 45: cannot create /dev/vc/0: Read-only file system /etc/rc.d/init.d/httpd: /etc/rc.d/init.d/httpd: 16: /sbin/boa: not found /etc/init.d/rcS: /etc/init.d/rcS: 48: cannot create /dev/vc/0: Read-only file system /etc/init.d/rcS: /etc/init.d/rcS: 49: cannot create /dev/vc/0: Read-only file system /etc/rc.d/init.d/leds: /etc/rc.d/init.d/leds: 16: /sbin/led-player: not found /etc/init.d/rcS: /etc/init.d/rcS: 52: cannot create /dev/vc/0: Read-only file system /etc/init.d/rcS: /etc/init.d/rcS: 53: cannot create /dev/vc/0: Read-only file system SIOCSIFADDR: No such device SIOCGIFFLAGS: No such device /etc/init.d/rcS: /etc/init.d/rcS: 59: /sbin/madplay: not found Please press Enter to activate this console. -sh: can't access tty; job control turned off id: unknown uid 0 [@FriendlyARM /]# uname -a Linux FriendlyARM 2.6.27-rc9 #8 Sat Oct 11 03:17:21 CST 2008 armv4tl unknown [@FriendlyARM /]# 第四天: 實現XP下虛擬機中的ubuntu利用gdb經過串口調試開發板上的2.6.27-rc9內核 問題,開發板只有一個串口,給gdb佔用了,怎麼操做開發板? 第五天: 實現硬盤安裝的ubuntu系統利用gdb經過串口調試開發板上的內核。 第六天: 移植cs8900a網卡驅動。 實現開發板從硬盤ubuntu的nfs啓動。 實現硬盤安裝的ubuntu系統利用gdb經過網口調試開發板上的內核。 參考: ubuntu8.04+skyeye1.2.4搭建linux2.6.24+s3c2410的模擬arm-linux開發環境 http://www.google.cn/search?complete=1&hl=zh-CN&newwindow=1&client=firefox-a&rls=org.mozilla:zh-CN:official&hs=R4b&q=cs8900+s3c2440+%E9%A9%B1%E5%8A%A8&start=20&sa=N http://blog.chinaunix.net/u2/72751/showart_1130655.html http://www.akae.cn/bbs/redirect.php?tid=6929&goto=lastpost
推薦這篇,內容很全: gdb 使用手冊 http://blog.chinaunix.net/u/11240/showart.php?id=340632
終極參考: Debugging with GDB http://sourceware.org/gdb/current/onlinedocs/gdb.html#SEC_Top
網址:
cgdb:http://cgdb.sourceforge.net/
kgdb:http://www.kdbg.org/screenshot.php
ddd:http://www.gnu.org/software/ddd/
insight:http://sourceware.org/insight/
這些工具在ubuntu下都有編譯好的.deb安裝包,利用「立新得」就直接搜索而後在線安裝。
這篇短文是個人淺陋之見,我接觸這些gui的時間也不久。錯誤不免。 虛擬機:qemu
內核內置kgdb
developer machine: 運行gdb
除了只用命令行gdb外,還能夠用gdb的gui,有
1.cgdb 缺點:界面簡陋,自動化程度低,只是把terminal分爲兩部分,上面部分顯示源碼,下面打命令。因爲沒有顯示反彙編的窗體,不適合要求使用到 stepi命令的場合。優勢:運行快,鍛鍊手指頭. 最大的優勢是,它有完美的代碼着色功能。其餘幾款調試器中都沒有。
2.ddd: 缺點:與kdbg相比,界面凌亂。優勢:代碼顯示效果比kdbg好,c和反彙編代碼分開在兩個窗口。 能夠隨時暫停程序的運行。data windows 這個功能很是強大靈活。提示 ddd –tty 2>/dev/null ./vmlinux ; remote target localhost:1234
3. kdbg: 缺點:功能比ddd弱。字體過小,c和反彙編代碼交錯顯示,反彙編代碼摺疊隱藏在C代碼之間,要顯示反彙編代碼要手動展開,不可忍受。太過界面化,竟然找不到是在哪裏手動打gdb命令。致命缺點是,內核跑起來後,若是沒有斷點攔截,就無法把內核的運行暫停下來,kdbg成了沒事姥,源碼窗口的顯示不更新。另外一個致命缺點是,若是沒有源碼只有二進制文件,雖然能夠下斷點,但沒法顯示反彙編代碼,沒意義。聽說kdbg是用來調試kde程序的,實際上也能調試內核。優勢:窗口能夠整合到一塊,穩定。有變化的寄存器會顯示紅色。提示 kdbg -r localhost:1234 ./vmlinux
4. insight: 和ddd都是基於TCL/TK,比較類似。優勢:源碼顯示功能最強,能夠選擇C和反彙編代碼分開和交叉顯示。能夠選擇反彙編代碼使用intel仍是at&t格式。能夠列出當前有哪些源文件,當前文件有哪些函數。變化的寄存器有改變顏色的功能,ddd則沒有。缺點:和ddd同樣,小窗口沒法整合到到窗口中,但比ddd差的是,主窗口最大化後小窗口沒法保持置頂。相對ddd的大劣勢是沒有一個強大的data windows。感受界面比ddd強大,但靈活性比ddd差點。對於調試內核來講,還有一個和kdbg相同的大缺點,內核只能經過斷點暫停運行,而ddd 下還能夠用ctrl+c暫停內核。另外它有個SB錯誤,顯示backtrace的窗口,標題竟然是stack. 提示: insight ./vmlinux
5. xxgdb: 古董級別。沒事幹的時候能夠玩玩
6. 其實,gdb自帶了一個基於curses的gui。啓動方式是gdbtui xxx; 或者在gdb啓動以後用命令layout啓動gui。很好用,能夠至多同時顯示三個分窗口。要是代碼有着色功能就行了。
針對內核調試的總結:
1. kdbg不適合調試內核
3. 若是想複習gdb強大的命令,選cgdb或純gdb。
4. 若是想學習彙編,insight是不二選擇。
5 若是傾向於把調試器看成瀏覽器使用,做爲source insight等工具的輔助工具,在內核運行中攔截函數,分析函數的調用關係,不須要反彙編的話,則cgdb是不錯的選擇 .(source insight等源碼分析工具備個共同的缺點,由於體系和內核配置不一樣,一個函數有不少的定義,藉助調試器能夠在內核運行的時候找出實際調用的那個)
6.insight和ddd很接近,各有千秋。但若是側重於追溯數據結構體間的聯繫,ddd更好一點,由於它有data window,它的強項是數據和數據結構關係分析並用圖像方式顯示出來(What is DDD? Data Display Debugger)。若是側重於分析彙編指令是怎麼在cpu中跑的,推薦用insight,由於它彙編代碼顯示功能更細緻。
7.惋惜目前在ubuntu8.04下,ddd+qemu組合用來調試驅動時有bug:驅動函數被攔截時若是正在qemu的系統下操做,鼠標就會凍結在qemu的屏幕中。其實調試單個驅動,用gdb就足夠了。ddd等gui通常用來調試理解內核原理。
另外有用的命令 ptype, whatis
----
更多相關技巧:
1. 獲取struct page結構的大小
(gdb) p mem_map $80 = (struct page *) 0xc1000000 (gdb) p mem_map+1 $81 = (struct page *) 0xc1000020 (gdb) p/x 0xc1000020 - 0xc1000000 $82 = 0×20
2.
打印前從指針mem_map所指起的5個page結構體
(gdb) p *mem_map@5 $83 = {{flags = 1024, _count = {counter = 1}, {_mapcount = {counter = -1}, {inuse = 65535, objects = 65535}}, {{private = 0, mapping = 0×0}, ptl =…
用ddd的圖形顯示命令是 (gdb) graph display *mem_map@5
參考 p *array@len
@的左邊是數組的首地址的值,也就是變量array所指向的內容,右邊則是數據的長度,其保存在變量len中
3.
每運行一次stepi/next等命令後顯示下一步要將要運行的反彙編指令
(gdb) display/i $pc 6: x/i $pc 0xc0144fb6 <init_cgroup_root+22>: mov %esp,%ebp (gdb) stepi 6: x/i $pc 0xc0144fb8 <init_cgroup_root+24>: mov %edx,0×44(%eax)
提示:display的管理:
undisplay delete display disable display enable display info display
4.使結構體的顯示更漂亮
(gdb) show print pretty Prettyprinting of structures is on. (gdb) set print pretty off (gdb) p *init_task->group_info $12 = {ngroups = 0, usage = {counter = 14}, small_block = {0 <repeats 32 times>}, nblocks = 0, blocks = 0xc0355530} (gdb) set print pretty on (gdb) p *init_task->group_info $13 = { ngroups = 0, usage = { counter = 14 }, small_block = {0 <repeats 32 times>}, nblocks = 0, blocks = 0xc0355530 }
(注:6.7.條來自http://techcenter.dicder.com/2006/0906/content_173.html)
5. 使用自定義命令。
(gdb) define nid Type commands for definition of 「nid」. End with a line saying just 「end」. >ni >disassemble $pc $pc+16 >end
6. 純gdb的多窗口顯示 GUI調試器能夠同時打開多個小窗口,分別顯示寄存器、彙編和源代碼等。在gdb裏也能夠作到,但同時最多隻能顯示兩個窗口,試了一下也很方便的。基本命令以下:
a) `layout src’ 僅顯示源代碼窗口。
b) `layout asm’ 僅顯示彙編代碼窗口。
c) `layout split’ 顯示源代碼和彙編代碼窗口。
d) `layout regs’ 顯示寄存器和源代碼窗口,或者寄存器和彙編代碼窗口。
e) `layout next` 和 `layout prev’ 切換窗口。
f) ctrl + L 刷新屏幕。
g) `C-x 1′ 單窗口模式。
h) `C-x 2′ 雙窗口模式。
i) `C-x a’ 回到傳統模式。
7. 字符gdb中,如何在每執行一次next命令後都自動顯示backtrace的內容 這個問題實際是如何一次執行多條命令。用自定義命令解決
(gdb) define nbt Type commands for definition of 「nbt」. End with a line saying just 「end」. >next >bt >end (gdb) nbt #0 early_cpu_init () at arch/x86/kernel/cpu/common.c:626 #1 0xc0384ca9 in setup_arch (cmdline_p=0xc0379fe8) at arch/x86/kernel/setup_32.c:765 #2 0xc037f62e in start_kernel () at init/main.c:564 #3 0xc037f008 in i386_start_kernel () at arch/x86/kernel/head32.c:13 #4 0×00000000 in ?? () (gdb)
8. gdb在TUI模式下如何把光標焦點炸轉移到command窗口,以便能用上下箭頭鍵能快速翻出歷史指令?
實際是轉換「active」窗口。 C-x o: ctrl+x,接着放開這兩個鍵,而後在按o(不須要+ctrl) 關於TUI更多信息: http://sourceware.org/gdb/current/onlinedocs/gdb_23.html#SEC236 還有組合鍵 C-x C-a C-x a C-x A 退出TUI模式 C-x 1 只用一個窗口 C-x 2 用兩個窗口,按屢次會有不一樣兩個窗口的組合形式 C-x o active 窗口轉移 C-x s 進入和退出TUI SingleKey 模式 注:C-x o屢次使用至關於依次執行如下命令 focus src 轉移焦點到源碼窗口。 focus asm focus regs focus cmd TUI模式還有如下專用命令 info win layout next layout prev layout src layout asm layout split layout regs focus next refresh tui reg float tui reg general tui reg next tui reg system update winheight name +count winheight name -count tabset nchars
9. 如何在子函數調用和退出時都暫停運行 watch $ebp
10. 如何獲取結構體中特定域的相對偏移量,好比struct stak_struct 中lock_depth的相對偏移量?
(gdb) p/x &(*(struct task_struct *)0).lock_depth $7 = 0x14
11. 如何可以交換使用ddd與gdb,也就是說使用ddd調試時,想換回使用純gdb,同時保證啓用gdb後保證「調試上下文」沒任何變化?
只要.gdbinit 文件沒包含 c, next..等等能驅動gdb繼續調試的命令就能夠。
12. 如何經過函數名肯定所在的源文件
(gdb) info line vfs_mkdir Line 2131 of "fs/namei.c" starts at address 0xc017c048 <vfs_mkdir> and ends at 0xc017c052 <vfs_mkdir+10>.
13. 由彙編指令地址肯定該指令所對應源碼的所在行(注:一行c語言通常對應幾行彙編指令)
info line *xxxxxxx (xxx是彙編指令地址)
14. 如何快速定位函數中某句C語句對應彙編指令的開始地址。好比如下 [內容太大,準備移到其餘位置]
2130 int vfs_mkdir(struct inode *dir, struct dentry *dentry, int mode) 2131 { ....... 2145 DQUOT_INIT(dir); 2146 error = dir->i_op->mkdir(dir, dentry, mode);//<-咱們想肯定這句語句的彙編指令開始地址,注意它在源文件中的行數 2147 if (!error) 2148 fsnotify_mkdir(dir, dentry); 2149 return error; 2150 }
首先,經過函數名查詢對應的源文件
(gdb) info line vfs_mkdir Line 2131 of "fs/namei.c" starts at address 0xc017c048 <vfs_mkdir> and ends at 0xc017c052 <vfs_mkdir+10>.
而後,利用info line 源文件:目標語句的行數 就能查詢到
(gdb) info line fs/namei.c:2146 Line 2146 of "fs/namei.c" starts at address 0xc017c0ee <vfs_mkdir+166> and ends at 0xc017c0fe <vfs_mkdir+182>.
驗證一下
(gdb) disass 0xc017c0ee Dump of assembler code for function vfs_mkdir: 0xc017c048 <vfs_mkdir+0>: push %ebp ..... 0xc017c0e4 <vfs_mkdir+156>: mov 0x24(%eax),%ecx 0xc017c0e7 <vfs_mkdir+159>: or $0xffffffff,%edx 0xc017c0ea <vfs_mkdir+162>: mov %esi,%eax 0xc017c0ec <vfs_mkdir+164>: call *(%ecx) 0xc017c0ee <vfs_mkdir+166>: mov 0x98(%esi),%ebx // 0xc017c0f4 <vfs_mkdir+172>: mov %edi,%edx //參數 dentry -> %edx 0xc017c0f6 <vfs_mkdir+174>: mov %esi,%eax //參數dir -> %eax 0xc017c0f8 <vfs_mkdir+176>: mov -0x10(%ebp),%ecx //參數mode -> %ecx 0xc017c0fb <vfs_mkdir+179>: call *0x14(%ebx) //dir->i_op->mkdir(dir, dentry, mode) 0xc017c0fe <vfs_mkdir+182>: test %eax,%eax //判斷返回值(error = dir->i_op->mkdir(dir, dentry, mode);) 0xc017c100 <vfs_mkdir+184>: mov %eax,%ebx //保存返回值 0xc017c102 <vfs_mkdir+186>: jne 0xc017c15d <vfs_mkdir+277> //若是返回值 != 0,也就是mkdir失敗,跳到最後返回。成功則繼續 0xc017c104 <vfs_mkdir+188>: testb $0x4,0x11c(%esi) //內聯函數fsnotify_mkdir 及子函數->inode_dir_notify在這裏展開 //static inline void inode_dir_notify(struct inode *inode, unsigned long event) //{ // if (inode->i_dnotify_mask & (event)) <-注意這裏判斷位,恰好對應testb $0x4,0x11c(%esi) 0xc017c10b <vfs_mkdir+195>: je 0xc017c119 <vfs_mkdir+209> ..... 0xc017c15d <vfs_mkdir+277>: lea -0xc(%ebp),%esp 0xc017c160 <vfs_mkdir+280>: mov %ebx,%eax
咱們經過mkdir參數個數,及testb 指令基本斷定咱們的猜想沒錯。也就是說vfs_mkdir函數中dir→i_op→mkdir的實際調用是在0xc017c0fb <vfs_mkdir+179>: call *0×14(%ebx)
15. 下斷點的形式
1. b 函數名 2. b *指令地址 3. b 源碼:行數 (gdb) b fs/namei.c:2146 Breakpoint 9 at 0xc017c0ee: file fs/namei.c, line 2146.
16. 陷入循環語句後,想自動運行到循環語句結束:
u
17. 重複當前的gdb指令
按enter鍵便可
本小節意義:爲了方便把調試內容複製出來,而又須要必定的功能,本人常用的工具是gdb的tui。因此gdb宏的使用更是成了不可缺乏的輔助手段。好比extendinstr宏,能實時顯示調用鏈的狀況,至關於實現了ddd的backtrace分窗口。其餘宏的做用就不說了。
kgdb官方的gdb宏 http://kgdb.linsyssoft.com/downloads.htm
「Fun with strace and the GDB Debugger」 http://www.ibm.com/developerworks/aix/library/au-unix-strace.html
「GNU Project Debugger: More fun with GDB」 http://www.ibm.com/developerworks/aix/library/au-gdb.html
「14.3.4. Useful Kernel gdb Macros」 from 「Embedded Linux Primer」http://book.opensourceproject.org.cn/embedded/embeddedprime/
假設要使用下節的lsmod,該gdb宏能列舉內核中的模塊。 在內核源碼目錄下創建一個新文件lsmod,內容見下節。
裝載宏 (gdb) source lsmod 查看說明 (gdb) help lsmod list module struct's address, text address and their module name 使用 (gdb) lsmod (gdb) lsmod Address text Module 0xE014DDA0 0xE014D000 nls_iso8859_1 0xE0169AE0 0xE0164000 isofs 0xE014BA20 0xE0148000 zlib_inflate 0xE0161FE0 0xE0152000 udf ..... 0xE0012DE0 0xE000B000 processor 0xE0008EA0 0xE0008000 fan 0xE00223E0 0xE0020000 thermal_sys ----end---- (gdb) (gdb) 咱們查看一下processor模塊結構體的內容 (gdb) p *(struct module *)0xE0012DE0 $10 = { state = MODULE_STATE_LIVE, list = { next = 0xe0008ea4, prev = 0xe0018984 }, name = "processor", '/0' <repeats 50 times>, mkobj = { kobj = { name = 0xd5910ba0 "processor", kref = { refcount = { counter = 3 } }, entry = { next = 0xe00189d0, ... ... 爲了方便查看該結構中指針域所指向的結構體,可在ddd下用如下命令打開數據圖形而後展開查看 (gdb) graph display *(struct module *)0xE0012DE0
給出的例子都在2.6.26內核上上測試經過。
宏名: lsmod(有小bug,飯後再看)
做用: 列舉內核模塊的名稱及對應模塊結構體的地址,以及text段的地址[todo,導出.bss,.data地址]
define lsmod printf "Address/t/ttext/t/tModule/n" set $m=(struct list_head *)&modules set $done=0 #獲取結構體內特定域的相對偏移,見"gdb技巧" set $offset=&(*(struct module *)0).list while ( !$done ) set $mp=(struct module *)((char *)$m->next - (char *)$offset) printf "0x%X/t0x%X/t%s/n", $mp, $mp->module_core,$mp->name if ( $mp->list->next == &modules) set $done=1 end set $m=$m->next end printf "----end----/n" end document lsmod list module struct's address, text address and their module name end
效果以下
(gdb) lsmod Address text Module 0xE014DDA0 0xE014D000 nls_iso8859_1 0xE0169AE0 0xE0164000 isofs 0xE014BA20 0xE0148000 zlib_inflate 0xE0161FE0 0xE0152000 udf ..... 0xE001BEA0 0xE001A000 8390 0xE017EEC0 0xE016C000 ide_core 0xE0018980 0xE0015000 thermal 0xE0012DE0 0xE000B000 processor 0xE0008EA0 0xE0008000 fan 0xE00223E0 0xE0020000 thermal_sys ----end----
宏名: psusr,pskern
做用: 列舉全部task的結構地址,狀態,PID,PPID,comm。
psusr,只列舉用戶層可見的進程;pskern,列舉內核層可見的全部進程。
define __show_state if ($arg0->state == 0) printf "running/t/t" else if ($arg0->state == 1) printf "sleeping/t" else if ($arg0->state == 2) printf "disksleep/t" else if ($arg0->state == 4) printf "zombie/t" else if ($arg0->state == 8) printf "stopped/t" else if ($arg0->state == 16) printf "wpaging/t" else printf "%d/t/t", $arg0->state end end end end end end end document __show_state internel macro, don't call it by hand end define psusr printf "address/t/tstate/t/tuid/tpid/tppid/tcomm/n" set $init_t = &init_task set $tasks_off=((size_t)&((struct task_struct *)0)->tasks) set $next_t=(((char *)($init_t->tasks).next) - $tasks_off) while ($next_t != $init_t) set $next_t=(struct task_struct *)$next_t printf "0x%08X/t", $next_t __show_state $next_t printf "%d/t%d/t%d/t%s/n", / $next_t->uid, $next_t->pid, / $next_t->parent->pid, $next_t->comm set $next_t=(char *)($next_t->tasks.next) - $tasks_off end printf "address/t/tstate/t/tuid/tpid/tppid/tcomm/n" printf "----end----/n" end document psusr print information for all tasks, but not including thread members. This command looks like "ps -aux" in userspace. end define pskern printf "address/t/tstate/t/tuid/tpid/tppid/tcomm/n" set $init_t = &init_task printf "0x%08X/t", $init_t __show_state $init_t printf "%d/t%d/t%d/t%s/n", / $init_t->uid, $init_t->pid, / $init_t->parent->pid, $init_t->comm set $tasks_off=((size_t)&((struct task_struct *)0)->tasks) set $thread_off=((size_t)&((struct task_struct *)0)->thread_group.next) set $next_t=(((char *)($init_t->tasks).next) - $tasks_off) while ($next_t != $init_t) set $next_t=(struct task_struct *)$next_t printf "0x%08X/t", $next_t __show_state $next_t printf "%d/t%d/t%d/t%s/n", / $next_t->uid, $next_t->pid, / $next_t->parent->pid, $next_t->comm set $next_th=(((char *)$next_t->thread_group.next) - $thread_off) while ($next_th != $next_t) set $next_th=(struct task_struct *)$next_th printf "0x%08X/t", $next_th __show_state $next_th printf "%d/t%d/t%d/t%s/n", / $next_th->uid, $next_th->pid, / $next_th->parent->pid, $next_th->comm set $next_th=(((char *)$next_th->thread_group.next) - $thread_off) end set $next_t=(char *)($next_t->tasks.next) - $tasks_off end printf "address/t/tstate/t/tuid/tpid/tppid/tcomm/n" printf "----end----/n" end document pskern print infor for all tasks viewed in kernel, including all thread members and swapper(PID==0). end
效果以下
(gdb) source ps (gdb) psusr address state uid pid ppid comm 0xDC43F8A0 sleeping 0 1 0 init 0xDC43F490 sleeping 0 2 0 kthreadd 0xDC43F080 sleeping 0 3 2 migration/0 0xDC43EC70 sleeping 0 4 2 ksoftirqd/0 0xDC43E860 sleeping 0 5 2 watchdog/0 ..... 0xDC44E060 sleeping 0 1707 1 acpid 0xD8AE6100 sleeping 104 1716 1 dbus-daemon 0xDC46ECD0 sleeping 0 1739 1 cupsd 0xDC45E080 sleeping 101 2009 1 exim4 0xD5A6C0E0 sleeping 0 2026 1 inetd 0xD5A6CD10 sleeping 0 2034 1 dhcdbd 0xDBD45160 sleeping 105 2044 1 hald 0xDBD45570 sleeping 0 2045 2044 hald-runner .... address state uid pid ppid comm ----end----
宏名: lssp
做用: 列舉超級塊地址及其s_id域
define lssp printf "address/t/ts_id/n" set $sb_lh=(struct list_head *)&super_blocks #獲取結構體內特定域的相對偏移,見"gdb技巧" set $offset=&(*(struct super_block *)0).s_list set $sbp=(struct super_block *)((char *)$sb_lh->next - (char *)$offset) while ( &$sbp->s_list != $sb_lh ) printf "0x%08X/t%s/n", $sbp, $sbp->s_id set $sbp=(struct super_block *)((char *)$sbp->s_list.next - (char *)$offset) end printf "----end----/n" end document lssp List the super_block and their start addresses end
效果
(gdb) lssp address s_id 0xDC40DC00 sysfs 0xDC40DA00 rootfs 0xDC40D800 bdev 0xDC40D400 proc 0xDC41B200 sockfs 0xDC431C00 debugfs 0xDC486600 pipefs 0xDC486000 anon_inodefs 0xD58C5A00 tmpfs 0xD58C5200 inotifyfs 0xD8C09800 devpts 0xD8C09600 hugetlbfs 0xD8C09400 mqueue 0xD590E000 tmpfs 0xD59E4C00 hda1 0xD5908A00 tmpfs 0xD7753200 tmpfs 0xDBD66400 hdc ----end----
宏名: eih, lih, ooi
做用: 克服時鐘中斷干擾與中斷無關的目標代碼的調試(X86下適用),解釋請看「工程方法」
說明: 使用gdb或ddd時,進入中斷後用finish命令的話經常是要麼沒法返回被中斷的原指令處後停住,而是繼續運行,要麼是會進入到另外一個時鐘中斷中;可是好像在insight下沒這個問題。使用這個gdb宏能夠解決該問題。
define eih b common_interrupt b native_iret end document eih eih: early interrupt hacking, break common_interrupt and native_iret end define lih b apic_timer_interrupt b irq_return end document lih lih: late interrupt hacking, break apic_timer_interrupt and irq_return end define ooi c stepi end document ooi ooi: out of interrupt, return to the instruction interrupted by interrupt handler end
宏名: extendinstr
做用: 擴展指令集。配合gdb自帶的tui使用,能代替ddd等界面工具的部分功能。
說明: 指令開頭:s→step,si→stepi,n→next,ni→nexti,中間bt→bt,末尾i→info args && info local
define inar printf "-----args start----/n" info args end define inlo printf "-----local start----/n" info local end define btl printf "-------------------/n" bt end define sibt stepi btl end define sbt step btl end define nibt nexti btl end define nbt next btl end define sibti inar inlo stepi btl end define sbti inar inlo step btl end define nibti inar inlo nexti btl end define nbti inar inlo next btl end
效果
宏名: quick
做用: 超級快捷鍵。gdb的快捷鍵並沒用用盡全部的按鍵。咱們能夠利用空餘的按鍵定義本身的命令。方便起見,我只是利用自定義命令簡單的實現該該功能,而不是自定義快捷鍵。能夠根據本身偏好來定義。
說明: 這個宏是配合前面的宏ooi和宏extendinstr使用的。這樣,若是調試時進入了時鐘中斷,按a+enter就能夠瞬間返回;q+enter–>sibt; z+enter–>finish。
define a ooi end define q sibt end define z finish end
宏名:bttnobp,btt,psusr,pskern,trapinfo,btpid,dmesg
內核文檔gdbmacros.txt 的gdb宏的升級版本,還修正了一個bug,已在2.6.26下測試。
若是你運行這個腳本有錯誤,那說明你的內核版本過低了,請運行內核源碼中原文件的宏。
本人這個文件的補丁還在提交的過程當中。
能提供non-running進程的backtrace功能,還實現了dmesg。
說明bttnobp沒在!CONFIG_FRAME_POINTER的配置下測試過,可是估計結果很不可靠,
由於條件判斷太寬大了。
# # This file contains a few gdb macros (user defined commands) to extract # useful information from kernel crashdump (kdump) like stack traces of # all the processes or a particular process and trapinfo. # # These macros can be used by copying this file in .gdbinit (put in home # directory or current directory) or by invoking gdb command with # --command=<command-file-name> option # # Credits: # Alexander Nyberg <alexn@telia.com> # V Srivatsa <vatsa@in.ibm.com> # Maneesh Soni <maneesh@in.ibm.com> # define __show_state if ($arg0->state == 0) printf "running/t/t" else if ($arg0->state == 1) printf "sleeping/t" else if ($arg0->state == 2) printf "disksleep/t" else if ($arg0->state == 4) printf "zombie/t" else if ($arg0->state == 8) printf "stopped/t" else if ($arg0->state == 16) printf "wpaging/t" else printf "%d/t/t", $arg0->state end end end end end end end document __show_state internel macro, don't call it by hand end define psusr printf "address/t/tstate/t/tuid/tpid/tppid/tcomm/n" set $init_t = &init_task set $tasks_off=((size_t)&((struct task_struct *)0)->tasks) set $next_t=(((char *)($init_t->tasks).next) - $tasks_off) while ($next_t != $init_t) set $next_t=(struct task_struct *)$next_t printf "0x%08X/t", $next_t __show_state $next_t printf "%d/t%d/t%d/t%s/n", / $next_t->uid, $next_t->pid, / $next_t->parent->pid, $next_t->comm set $next_t=(char *)($next_t->tasks.next) - $tasks_off end printf "address/t/tstate/t/tuid/tpid/tppid/tcomm/n" printf "----end----/n" end document psusr print information for all tasks, but not including thread members. This command looks like "ps -aux" in userspace. end define pskern printf "address/t/tstate/t/tuid/tpid/tppid/tcomm/n" set $init_t = &init_task printf "0x%08X/t", $init_t __show_state $init_t printf "%d/t%d/t%d/t%s/n", / $init_t->uid, $init_t->pid, / $init_t->parent->pid, $init_t->comm set $tasks_off=((size_t)&((struct task_struct *)0)->tasks) set $thread_off=((size_t)&((struct task_struct *)0)->thread_group.next) set $next_t=(((char *)($init_t->tasks).next) - $tasks_off) while ($next_t != $init_t) set $next_t=(struct task_struct *)$next_t printf "0x%08X/t", $next_t __show_state $next_t printf "%d/t%d/t%d/t%s/n", / $next_t->uid, $next_t->pid, / $next_t->parent->pid, $next_t->comm set $next_th=(((char *)$next_t->thread_group.next) - $thread_off) while ($next_th != $next_t) set $next_th=(struct task_struct *)$next_th printf "0x%08X/t", $next_th __show_state $next_th printf "%d/t%d/t%d/t%s/n", / $next_th->uid, $next_th->pid, / $next_th->parent->pid, $next_th->comm set $next_th=(((char *)$next_th->thread_group.next) - $thread_off) end set $next_t=(char *)($next_t->tasks.next) - $tasks_off end printf "address/t/tstate/t/tuid/tpid/tppid/tcomm/n" printf "----end----/n" end document pskern print infor for all tasks viewed in kernel, including all thread members and swapper(PID==0). end define __prinfo_nobp printf "/npid %d; addr:0x%08x; comm %s:/n", / $arg0.pid, $arg0, $arg0.comm printf "=====================================/n" set var $stackp = $arg0.thread.sp set var $stack_top = ($stackp & ~4095) + 4096 while ($stackp < $stack_top) if (*($stackp) > _stext && *($stackp) < _sinittext) info symbol *($stackp) end set $stackp += 4 end end document __prinfo_nobp internal macro, don't call it by hand. end define bttnobp set $tasks_off=((size_t)&((struct task_struct *)0)->tasks) set $thread_off=((size_t)&((struct task_struct *)0)->thread_group.next) set $init_t=&init_task set $next_t=(((char *)($init_t->tasks).next) - $tasks_off) while ($next_t != $init_t) set $next_t=(struct task_struct *)$next_t __prinfo_nobp $next_t set $next_th=(((char *)$next_t->thread_group.next) - $thread_off) while ($next_th != $next_t) set $next_th=(struct task_struct *)$next_th __prinfo_nobp $next_th set $next_th=(((char *)$next_th->thread_group.next) - $thread_off) end set $next_t=(char *)($next_t->tasks.next) - $tasks_off end end document bttnobp dump all thread stack traces on a kernel compiled with !CONFIG_FRAME_POINTER end define __prinfo printf "/npid %d; addr:0x%08x; comm %s:/n", / $arg0.pid, $arg0, $arg0.comm printf "=====================================/n" set var $stackp = $arg0.thread.sp set var $stack_top = ($stackp & ~4095) + 4096 set var $stack_bot = ($stackp & ~4095) set $stackp = *($stackp) while (($stackp < $stack_top) && ($stackp > $stack_bot)) set var $addr = *($stackp + 4) info symbol $addr set $stackp = *($stackp) end end document __prinfo internal macro, don't call it by hand. end define btt set $tasks_off=((size_t)&((struct task_struct *)0)->tasks) set $thread_off=((size_t)&((struct task_struct *)0)->thread_group.next) set $init_t=&init_task set $next_t=(((char *)($init_t->tasks).next) - $tasks_off) while ($next_t != $init_t) set $next_t=(struct task_struct *)$next_t __prinfo $next_t set $next_th=(((char *)$next_t->thread_group.next) - $thread_off) while ($next_th != $next_t) set $next_th=(struct task_struct *)$next_th __prinfo $next_th set $next_th=(((char *)$next_th->thread_group.next) - $thread_off) end set $next_t=(char *)($next_t->tasks.next) - $tasks_off end end document btt dump all thread stack traces on a kernel compiled with CONFIG_FRAME_POINTER end define btpid set var $pid = $arg0 set $tasks_off=((size_t)&((struct task_struct *)0)->tasks) set $thread_off=((size_t)&((struct task_struct *)0)->thread_group) set $init_t=&init_task set $next_t=(((char *)($init_t->tasks).next) - $tasks_off) set var $pid_task = 0 while ($next_t != $init_t) set $next_t=(struct task_struct *)$next_t if ($next_t.pid == $pid) set $pid_task = $next_t end set $next_th=(((char *)$next_t->thread_group.next) - $thread_off) while ($next_th != $next_t) set $next_th=(struct task_struct *)$next_th if ($next_th.pid == $pid) set $pid_task = $next_th end set $next_th=(((char *)$next_th->thread_group.next) - $thread_off) end set $next_t=(char *)($next_t->tasks.next) - $tasks_off end __prinfo $pid_task end document btpid backtrace of pid end define trapinfo set var $pid = $arg0 set $tasks_off=((size_t)&((struct task_struct *)0)->tasks) set $thread_off=((size_t)&((struct task_struct *)0)->thread_group.next) set $init_t=&init_task set $next_t=(((char *)($init_t->tasks).next) - $tasks_off) set var $pid_task = 0 while ($next_t != $init_t) set $next_t=(struct task_struct *)$next_t if ($next_t.pid == $pid) set $pid_task = $next_t end set $next_th=(((char *)$next_t->thread_group.next) - $thread_off) while ($next_th != $next_t) set $next_th=(struct task_struct *)$next_th if ($next_th.pid == $pid) set $pid_task = $next_th end set $next_th=(((char *)$next_th->thread_group.next) - $thread_off) end set $next_t=(char *)($next_t->tasks.next) - $tasks_off end printf "Trapno %ld, cr2 0x%lx, error_code %ld/n", $pid_task.thread.trap_no, / $pid_task.thread.cr2, $pid_task.thread.error_code end document trapinfo Run info threads and lookup pid of thread #1 'trapinfo <pid>' will tell you by which trap & possibly address the kernel panicked. end define dmesg set $i = 0 set $end_idx = (log_end - 1) & (log_buf_len - 1) while ($i < logged_chars) set $idx = (log_end - 1 - logged_chars + $i) & (log_buf_len - 1) if ($idx + 100 <= $end_idx) || / ($end_idx <= $idx && $idx + 100 < log_buf_len) printf "%.100s", &log_buf[$idx] set $i = $i + 100 else printf "%c", log_buf[$idx] set $i = $i + 1 end end end document dmesg print the kernel ring buffer end
宏名:vmap, lsvmaps, lsmod, lsmodsects, lsallmodsects
說明:沒測試,待更新
來源 http://jeanmarc.saffroy.free.fr/kdump2gdb/
# Copyright Jean-Marc Saffroy <saffroy@gmail.com> 2006 # This program is free software, distributed under the terms of the # GNU General Public License version 2. # a few useful(?) macros for x86-64 VMM hacks # useful constants set $PAGE_SIZE = (1<<12) set $__PHYSICAL_MASK = (1 << 46)-1 set $PTE_MASK = ~($PAGE_SIZE-1) & $__PHYSICAL_MASK set $__PAGE_OFFSET = 0xffff810000000000 set $_PAGE_PSE = 0x80 define vmap set $addr = (long)$arg0 # index in each of the 4 levels of page directories set $pgd = $addr >> 39 & (1<<9)-1 set $pud = $addr >> 30 & (1<<9)-1 set $pmd = $addr >> 21 & (1<<9)-1 set $pte = $addr >> 12 & (1<<9)-1 # offset in page set $off = $addr & (1<<12)-1 #printf "%03x %03x %03x %03x %03x/n", $pgd, $pud, $pmd, $pte, $off set $pgd_off = (pgd_t *) &init_level4_pgt + $pgd #printf "pgd_off: %lx pgd: %lx/n", $pgd_off, (long)$pgd_off->pgd set $pgd_page = ((long)$pgd_off->pgd & $PTE_MASK) + $__PAGE_OFFSET #printf "pgd_page: %lx/n", $pgd_page set $pud_off = ((pud_t *) $pgd_page) + $pud #printf "pud_off: %lx pud: %lx/n", $pud_off, (long)$pud_off->pud
注意:某些內容不具有廣泛性。好比給出的反彙編代碼,在不一樣的優化等級下是不一樣的。可是在熟悉了典型的函數調用鏈反彙編代碼,對於有變化的其餘形式也就不難理解了。
Intel® 64 and IA-32 Architectures Software Developer’s Manuals
參考
「AT&T彙編語言與GCC內嵌彙編簡介」 http://blog.chinaunix.net/u2/73528/showart_1110874.html
[雜類文章]
「Linux Assembly and Disassembly an Introduction」 http://www.milw0rm.com/papers/47
GCC-Inline-Assembly-HOWTO http://www.ibiblio.org/gferg/ldp/GCC-Inline-Assembly-HOWTO.html
參考文章 [多如牛毛]
「Guide: Function Calling Conventions」 http://www.delorie.com/djgpp/doc/ug/asm/calling.html
「Intel x86 Function-call Conventions - Assembly View」 http://www.unixwiz.net/techtips/win32-callconv-asm.html
「C Function Call Conventions and the Stack」 http://www.cs.umbc.edu/~chang/cs313.s02/stack.shtml
「The C Calling Convention and the 8086: Using the Stack Frame」http://www.et.byu.edu/groups/ece425web/stable/labs/StackFrame.html
「C Function Calling Convention」 http://adamw-dev.blogspot.com/2007/05/c-function-calling-convention.html
「C函數調用在GNU彙編中的實現」 http://www.unixresources.net/linux/clf/cpu/archive/00/00/59/75/597564.html
「函數調用的幾個概念:_stdcall,_cdecl....」 http://blog.chinaunix.net/u2/67530/showart_601750.html
「Calling conventions(調用規則)」 http://www.bobd.cn/itschool/Program/delphi/200612/itschool_12084.html
[擴展,簡要說明原理。並用實例解析]
x86終極參考
CHAPTER 6 PROCEDURE CALLS, INTERRUPTS, AND EXCEPTIONS of
IA-32 Intel_ Architecture Software Developer’s Manual Volume 1_ Basic Architecture.pdfhttp://download.intel.com/design/processor/manuals/253665.pdf
1. %esp: 棧指針
指向棧的頂端,也就是指向棧的最後一個正在使用的元素。%esp的值隱式地受到幾個機器指令的影響,好比push,pop,call,ret等。
2. %ebp: 基址指針
指向當前棧的基地址,有時也稱爲「幀指針」。與%esp不一樣的是,它必須顯式地進行操做才能改變值。
3. %eip: 指令指針
保存着下一個被執行機器指令的地址。當CPU執行call指令時,%eip的值自動被保存到棧中。還有,任何一個「jump」跳轉指令都會直接地改變%eip
1. gcc要求在函數調用的先後,寄存器%ebx,%esi,%edi,%ebp,%esp,%ds, %es,%ss的值保持不變。因此被調用函數若是須要修改這些寄存器的值,被調用函數必須負責對它們進行保護。[後三個??]
2. gcc規定在函數調用的先後,寄存器%eax,%edx,%ecx的值能夠改變。因此調用函數若是須要防止子函數破壞這三個寄存器的值,調用者必須在函數調用前本身負責保護它們。
咱們注意到,是保護,不必定是保存。若是確認沒用到某寄存器,那麼該寄存器就不須要必定要有一個先保存到棧然後再恢復原值的過程。
這兩條規則實際是定義了對系統資源使用的權限和義務。
第一條規則,是銀行和借貸者的關係。有人向銀行借了幾千萬,結果賭博全輸光了。還錢的期限到了,銀行的行長對借貸者說「沒事,你回家吧。幾千萬而已,我拿我工資給你墊上」。我想這樣的事決不會發生,行長一個電話110過去,借貸者一天後就把錢還清了。因此,這裏,調用函數是銀行行長,子函數是借貸者。
第二條規則,則是老爸和兒子的關係了。兒子對老爸說「老爸,解我100去買球鞋,我明天還你」。結果,次日,老爸沒錢吃飯了,問兒子「還錢」。兒子說「昨晚逛街碰到一個美女,請了一頓,把錢化光了」。老爸無法子,總不能把兒子繩以正法吧。怪只能怪本身事前沒防這招咯。因此,這裏,調用函數是老爸,子函數是兒子你。
1. Integers (of any size up to 32 bits) and pointers are returned in the %eax register. 2. Floating point values are returned in the 387 top-of-stack register, st(0). 3. Return values of type long long int are returned in %edx:%eax (the most significant word in %edx and the least significant in %eax). 4. Returning a structure is complicated and rarely useful; try to avoid it. (Note that this is different from returning a pointer to a structure.) 5. If your function returns void (e.g. no value), the contents of these registers are not used.
咱們回頭看看「寄存器的角色」這一小節,很快就能明白調用鏈的造成的本質。
調用鏈包含兩方面的內容
1.返回地址的保存與恢復
2.舊棧幀的保存與恢復
由於在普通的調用形式中(call調用),返回地址的保存與恢復是由處理器機制自己保證的,不需人工維護。調用指令call的執行自動將call指令之下的指令地址壓入棧中,被調用函數返回時,ret指令的執行會從新將返回地址從棧彈出傳送到pc中。要求下面分析舊棧幀的保存與恢復。
舊棧幀的保存與恢復,無非就是要解決兩大問題:
1. 創建新棧幀 這一步很簡單,棧幀無非有兩個頭,底端和頂端。%esp指向棧的頂端,而%esp是不須要手工維護的,隨着push,pop等指令,它本身就在改變本身。那麼又怎麼創建棧幀的底端呢?咱們知道,棧底(也就是基址)是由%ebp指定的,在一個棧幀的整個生命週期裏,%ebp的值都不變,也就是說,賦個合適的值給它就完事。怎麼賦值就是問題所在了。咱們知道,%esp指向棧中最後一個被使用的元素。因此,當咱們正在使用(咱們認爲的)第一個元素時,把%esp的值賦給%ebp,%ebp不就是指向棧的基址了嗎?
2. 保護舊棧幀的信息 一樣的問題,保護舊棧幀的信息,就是保存舊棧幀指向底端和頂端的指針值,也就是舊%ebp,%esbp的值。當函數調用指令剛執行完,立刻就要保護做案現場了。首先,push %ebp,這句就把舊棧幀的基地址保存在棧的頂端。此時,%esp指向的內存地址中,就放着舊棧幀的基地址的值。可是還不夠啊,%esp是個不可靠的東西,它常常在變化,必須把這個地址放到一個不會隱式變化的寄存器中。因而選擇了%ebp。mov %esp %ebp.這樣,%ebp指向的內存地址中,就放着舊棧幀的基地址的值。這就解放了%esp,能夠用%esp來動態指向新棧幀的頂端了。按照定義,%ebp所指向的地址是新棧幀的底端,也就是新棧幀的第一個元素,也就是說新棧幀第一個元素的值是舊棧幀基址。
可是注意,%ebp指向的地址再加4bytes的地址上,存放的是被調用函數的返回地址。在執行call指令時,call指令後面的那個指令的地址(也就是被調用函數的返回地址)被自動隱式地放到了棧中。
當子函數返回時,再按照上面文字進行逆操做,就能恢復舊棧幀的信息。
#include <stdio.h> void func() {} void funb() { func(); } void funa() { funb(); } int main() { funa(); } ------- 08048344 <func>: #include <stdio.h> void func() {} 8048344: 55 push %ebp 8048345: 89 e5 mov %esp,%ebp 8048347: 5d pop %ebp 8048348: c3 ret 08048349 <funb>: void funb() { 8048349: 55 push %ebp 804834a: 89 e5 mov %esp,%ebp func(); 804834c: e8 f3 ff ff ff call 8048344 <func> } 8048351: 5d pop %ebp 8048352: c3 ret 08048353 <funa>: void funa() { 8048353: 55 push %ebp 8048354: 89 e5 mov %esp,%ebp funb(); 8048356: e8 ee ff ff ff call 8048349 <funb> } 804835b: 5d pop %ebp 804835c: c3 ret 0804835d <main>: int main() { 804835d: 8d 4c 24 04 lea 0x4(%esp),%ecx 8048361: 83 e4 f0 and $0xfffffff0,%esp 8048364: ff 71 fc pushl -0x4(%ecx) 8048367: 55 push %ebp 8048368: 89 e5 mov %esp,%ebp 804836a: 51 push %ecx funa(); 804836b: e8 e3 ff ff ff call 8048353 <funa> } 8048370: 59 pop %ecx 8048371: 5d pop %ebp 8048372: 8d 61 fc lea -0x4(%ecx),%esp 8048375: c3 ret 8048376: 90 nop 8048377: 90 nop 8048378: 90 nop 8048379: 90 nop 804837a: 90 nop 804837b: 90 nop 804837c: 90 nop 804837d: 90 nop 804837e: 90 nop 804837f: 90 nop func被調用後內存以下 | | | | | | hight | | | | | +--------------/ | +---+ main's %ebp |/ | +-> +--------------+ --funa's frame | | | ret to funa | / | | +--------------+X | +---+ funa's %ebp | / | +-->+--------------+ ---funb's frame | | | ret to funb | / | | +--------------+ | +---+ funb's %ebp |<---func's frame | low %esp--> +--------------+<---- %ebp v | | | | | | | | | |
爲了說明就近原則,咱們先看看典型和全面的棧幀是怎樣的。函數caller調用子函數callee所造成的棧幀。
1. 從被調用的子函數callee來看,獲取caller的傳遞的實參,以及創建自身本地變量時,由於內存地址都靠近棧幀的基址,因此這兩種引用都是利用%ebp加上偏移量的形式。
2. 相反,主函數在調用子函數前,在爲子函數準備實參時,由於實參位於棧幀末端,因此對實參的引用都是利用%esp加上偏移量的形式(沒畫出來)
caller's frame pointer | | | | | | | | | | | +-------------------+ | | caller saved | | | registers | | | %eax,%ecx,%edx | | | (as needed) | | +-------------------+ | | argument #3 | [%ebp+16] | +-------------------+ | | argument #2 | [%ebp+12] | +-------------------+ | | argument #1 | [%ebp+8] | +-------------------+ | | return address | | +-------------------+ ----- +-----+ caller's %ebp |<---%ebp / +-------------------+ / | local var #1 | [%ebp-4] / +-------------------+ | | local var #2 | [%ebp-8] | +-------------------+ | | temporary | | | storage | | +-------------------+ | callee saved | callee stack frame | registers | | | %ebx,%esi,%edi | | | (as needed) | | +-------------------+ | | | | | | | | | / | |<----%esp / | caller:調用者 callee:被調用者
函數caller調用子函數callee,這是應用層的普通函數調用過程。若是是遠調用,跨態調用要考慮的東西更多。但這個例子已經充分展現了調用過程的繁複部分。
1.%eax,%edx,%ecx入棧(可選)
2.子函數的參數入棧
call機器指令,原子性自動地完成了兩種任務.
1.%eip入棧, 保存了callee函數的返回地址
2.callee的函數地址傳遞到%eip.
因此下一指令就從callee函數的第一指令開始運行。控制權轉移給callee
1.保存caller棧幀基址 push %ebp
2.創建callee棧幀基址 mov %esp,%ebp
3.分配本地變量和臨時存儲的空間 sub $XXX, %esp
4.本地變量賦值
5.%ebx,%esi,%edi入棧(可選)
1.%ebx,%esi,%edi還原(出棧,可選)
2.釋放本地變量和臨時存儲的棧空間mov %ebp,%esp
3.還原caller棧幀的基址 pop %ebp
或者2.3.步用一條元語指令完成 leave
4.調用返回 ret
該指令把存放於棧的返回地址取出(出棧),存放到%eip中。下一指令就從call callee指令的下一指令開始運行。控制權返回給caller
1.釋放存放callee參數的棧空間 add $XXX, %esp
2.轉移%eax的值(子函數的返回值,可選)
3.還原%eax,%edx,%ecx(出棧,可選)
應用層參數的傳入: 用戶層參數的傳遞是利用棧來完成的。函數右邊的參數先入棧,位於棧的高地址。反之, 函數左邊的參數後入棧,位於棧的低地址。
例子請看 「C難點的彙編解釋」
內核層參數的傳入: 混合使用寄存器和棧來傳遞參數。當參數個數很少於3個時,參數從左到右依次傳遞到%eax, %edx, %ecx.當參數個數多於3時,從第4個起的其他參數經過棧傳遞。一樣,函數右邊的參數先入棧,位於棧的高地址。反之, 函數左邊的參數後入棧,位於棧的低地址。
系統調用的參數傳遞:[之後再看]
C庫函數 ssize_t read(int fd, void *buf, size_t count); 000b6a30 <__read>: b6a30: 65 83 3d 0c 00 00 00 cmpl $0x0,%gs:0xc b6a37: 00 b6a38: 75 1d jne b6a57 <__read+0x27> b6a3a: 53 push %ebx b6a3b: 8b 54 24 10 mov 0x10(%esp),%edx //count b6a3f: 8b 4c 24 0c mov 0xc(%esp),%ecx //buf b6a43: 8b 5c 24 08 mov 0x8(%esp),%ebx //fd b6a47: b8 03 00 00 00 mov $0x3,%eax //系統調用號 b6a4c: cd 80 int $0x80 b6a4e: 5b pop %ebx b6a4f: 3d 01 f0 ff ff cmp $0xfffff001,%eax b6a54: 73 2d jae b6a83 <__read+0x53> b6a56: c3 ret b6a57: e8 14 ae 01 00 call d1870 <pthread_exit+0x110> b6a5c: 50 push %eax b6a5d: 53 push %ebx b6a5e: 8b 54 24 14 mov 0x14(%esp),%edx b6a62: 8b 4c 24 10 mov 0x10(%esp),%ecx b6a66: 8b 5c 24 0c mov 0xc(%esp),%ebx b6a6a: b8 03 00 00 00 mov $0x3,%eax b6a6f: cd 80 int $0x80 b6a71: 5b pop %ebx b6a72: 87 04 24 xchg %eax,(%esp) b6a75: e8 c6 ad 01 00 call d1840 <pthread_exit+0xe0> b6a7a: 58 pop %eax b6a7b: 3d 01 f0 ff ff cmp $0xfffff001,%eax b6a80: 73 01 jae b6a83 <__read+0x53> b6a82: c3 ret b6a83: e8 8e 5a 04 00 call fc516 <__frame_state_for+0xb96> b6a88: 81 c1 6c e5 07 00 add $0x7e56c,%ecx b6a8e: 8b 89 e0 ff ff ff mov -0x20(%ecx),%ecx b6a94: 31 d2 xor %edx,%edx b6a96: 29 c2 sub %eax,%edx b6a98: 65 03 0d 00 00 00 00 add %gs:0x0,%ecx b6a9f: 89 11 mov %edx,(%ecx) b6aa1: 83 c8 ff or $0xffffffff,%eax b6aa4: eb dc jmp b6a82 <__read+0x52> b6aa6: 90 nop 調用號#define __NR_read 3 (gdb) disass sys_read Dump of assembler code for function sys_read: 0xc017585a <sys_read+0>: push %ebp 0xc017585b <sys_read+1>: mov %esp,%ebp 0xc017585d <sys_read+3>: push %esi 0xc017585e <sys_read+4>: mov $0xfffffff7,%esi 0xc0175863 <sys_read+9>: push %ebx 0xc0175864 <sys_read+10>: sub $0xc,%esp 0xc0175867 <sys_read+13>: mov 0x8(%ebp),%eax 0xc017586a <sys_read+16>: lea -0xc(%ebp),%edx 0xc017586d <sys_read+19>: call 0xc0175f65 <fget_light> 0xc0175872 <sys_read+24>: test %eax,%eax 0xc0175874 <sys_read+26>: mov %eax,%ebx 0xc0175876 <sys_read+28>: je 0xc01758b1 <sys_read+87> 0xc0175878 <sys_read+30>: mov 0x24(%ebx),%edx 0xc017587b <sys_read+33>: mov 0x20(%eax),%eax 0xc017587e <sys_read+36>: mov 0x10(%ebp),%ecx 0xc0175881 <sys_read+39>: mov %edx,-0x10(%ebp) 0xc0175884 <sys_read+42>: mov 0xc(%ebp),%edx 0xc0175887 <sys_read+45>: mov %eax,-0x14(%ebp) 0xc017588a <sys_read+48>: lea -0x14(%ebp),%eax 0xc017588d <sys_read+51>: push %eax 0xc017588e <sys_read+52>: mov %ebx,%eax 0xc0175890 <sys_read+54>: call 0xc01753c1 <vfs_read> 0xc0175895 <sys_read+59>: mov -0x10(%ebp),%edx 0xc0175898 <sys_read+62>: mov %eax,%esi 0xc017589a <sys_read+64>: mov -0x14(%ebp),%eax 0xc017589d <sys_read+67>: mov %edx,0x24(%ebx) 0xc01758a0 <sys_read+70>: mov %eax,0x20(%ebx) 0xc01758a3 <sys_read+73>: cmpl $0x0,-0xc(%ebp) 0xc01758a7 <sys_read+77>: pop %eax 0xc01758a8 <sys_read+78>: je 0xc01758b1 <sys_read+87> 0xc01758aa <sys_read+80>: mov %ebx,%eax 0xc01758ac <sys_read+82>: call 0xc0175eae <fput> 0xc01758b1 <sys_read+87>: lea -0x8(%ebp),%esp 0xc01758b4 <sys_read+90>: mov %esi,%eax 0xc01758b6 <sys_read+92>: pop %ebx 0xc01758b7 <sys_read+93>: pop %esi 0xc01758b8 <sys_read+94>: pop %ebp 0xc01758b9 <sys_read+95>: ret End of assembler dump. (gdb) list fget_light 313 * holds a refcnt to that file. That check has to be done at fget() only 314 * and a flag is returned to be passed to the corresponding fput_light(). 315 * There must not be a cloning between an fget_light/fput_light pair. 316 */ 317 struct file *fget_light(unsigned int fd, int *fput_needed) 來自2.6.11 378 #define _syscall6(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4, / 379 type5,arg5,type6,arg6) / 380 type name (type1 arg1,type2 arg2,type3 arg3,type4 arg4,type5 arg5,type6 arg6) / 381 { / 382 long __res; / 383 __asm__ volatile ("push %%ebp ; movl %%eax,%%ebp ; movl %1,%%eax ; int $0x80 ; pop %%ebp" / 384 : "=a" (__res) / 385 : "i" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), / 386 "d" ((long)(arg3)),"S" ((long)(arg4)),"D" ((long)(arg5)), / 387 "0" ((long)(arg6))); / 388 __syscall_return(type,__res); / 389 }
內核中(x86)對調用鏈的回溯的代碼實如今文件dumpstack_32.c文件中。主要函數是dump_trace和print_context_stack.
待解釋
if ... else if
這個例子有人看來也許是很是很是地簡單,但就這個例子,有的人還真給我考」倒」了。他的回話是「還真沒見過這樣子的代碼」。可是,這樣的代碼在內核中比比皆是,好比後面附上的函數代碼 do_path_lookup。若是對if ... else if 理解有誤差,對內核代碼的邏輯理解根本就是差以千里。
#include <stdio.h> int main() { int i = 1; int j = 2; if (i == 1) printf("i,ok/n"); else if (j == 2) printf("j,ok/n"); return 0; }
這個例子,有人會疑問爲何」j,ok」沒打印出來。如今咱們分析下它的彙編代碼
08048374 <main>: 8048374: 8d 4c 24 04 lea 0x4(%esp),%ecx 8048378: 83 e4 f0 and $0xfffffff0,%esp 804837b: ff 71 fc pushl -0x4(%ecx) 804837e: 55 push %ebp 804837f: 89 e5 mov %esp,%ebp //以上彙編碼保存舊棧幀信息,創建新棧幀 8048381: 51 push %ecx //%ecx入棧保護 8048382: 83 ec 14 sub $0x14,%esp //創建本地變量棧空間,以及子函數實參棧空間 8048385: c7 45 f8 01 00 00 00 movl $0x1,-0x8(%ebp) //變量i賦值,記得本地變量的地址靠近棧幀的基地址,因此用%ebp引用 804838c: c7 45 f4 02 00 00 00 movl $0x2,-0xc(%ebp) //變量j賦值 8048393: 83 7d f8 01 cmpl $0x1,-0x8(%ebp) //i和1比較 8048397: 75 0e jne 80483a7 <main+0x33> //若是i-1不等0,跳到地址80483a7執行。不然繼續執行下面指令 8048399: c7 04 24 90 84 04 08 movl $0x8048490,(%esp) //printf函數第一個參數入棧,它的棧空間以前已經建好。 //記得子函數的實參空間靠近棧頂,因此引用實參用%esp 80483a0: e8 2f ff ff ff call 80482d4 <puts@plt> //調用printf 80483a5: eb 12 jmp 80483b9 <main+0x45> //printf返回後,接着執行這個指令,將跳到地址80483b9繼續運行 80483a7: 83 7d f4 02 cmpl $0x2,-0xc(%ebp) 80483ab: 75 0c jne 80483b9 <main+0x45> 80483ad: c7 04 24 95 84 04 08 movl $0x8048495,(%esp) 80483b4: e8 1b ff ff ff call 80482d4 <puts@plt> 80483b9: b8 00 00 00 00 mov $0x0,%eax //%eax賦值0,%eax放的也就是main函數返回結果 80483be: 83 c4 14 add $0x14,%esp //撤銷新棧幀的本地變量棧空間,以及子函數實參棧空間 80483c1: 59 pop %ecx //恢復保存的舊%ecx的值 80483c2: 5d pop %ebp //如下彙編碼都是恢復舊棧幀的信息,main函數返回等 80483c3: 8d 61 fc lea -0x4(%ecx),%esp 80483c6: c3 ret
通過上面的彙編代碼分析,可見c代碼塊
else if (j == 2) printf("j,ok/n");
對應的彙編代碼是:
80483a7: 83 7d f4 02 cmpl $0x2,-0xc(%ebp) 80483ab: 75 0c jne 80483b9 <main+0x45> 80483ad: c7 04 24 95 84 04 08 movl $0x8048495,(%esp) 80483b4: e8 1b ff ff ff call 80482d4 <puts@plt>
上面的代碼指令根本就沒有機會運行。
結論,一個if ... else if ..else..
if (判斷語句1) 代碼塊1 else if (判斷語句2) 代碼塊2; else if .... .. else 代碼塊N;
語句塊1,2..N的運行機會是一種互斥的關係。固然它們的「機會優先級」是不同的。語句塊1,2..N只有一個有被運行的機會,若是沒有else甚至可能沒有一個語句塊能被運行。
內核代碼實例
static int do_path_lookup(int dfd, const char *name, unsigned int flags, struct nameidata *nd) { int retval = 0; int fput_needed; struct file *file; struct fs_struct *fs = current->fs; nd->last_type = LAST_ROOT; /* if there are only slashes... */ nd->flags = flags; nd->depth = 0; if (*name=='/') { read_lock(&fs->lock); if (fs->altroot.dentry && !(nd->flags & LOOKUP_NOALT)) { nd->path = fs->altroot; path_get(&fs->altroot); read_unlock(&fs->lock); if (__emul_lookup_dentry(name,nd)) goto out; /* found in altroot */ read_lock(&fs->lock); } nd->path = fs->root; path_get(&fs->root); read_unlock(&fs->lock); } else if (dfd == AT_FDCWD) { read_lock(&fs->lock); nd->path = fs->pwd; path_get(&fs->pwd); read_unlock(&fs->lock); } else { struct dentry *dentry; file = fget_light(dfd, &fput_needed); retval = -EBADF; if (!file) goto out_fail; dentry = file->f_path.dentry; retval = -ENOTDIR; if (!S_ISDIR(dentry->d_inode->i_mode)) goto fput_fail; retval = file_permission(file, MAY_EXEC); if (retval) goto fput_fail; nd->path = file->f_path; path_get(&file->f_path); fput_light(file, fput_needed); } retval = path_walk(name, nd); out: if (unlikely(!retval && !audit_dummy_context() && nd->path.dentry && nd->path.dentry->d_inode)) audit_inode(name, nd->path.dentry); out_fail: return retval; fput_fail: fput_light(file, fput_needed); goto out_fail; }
短路邏輯算法。
這樣的例子在內核代碼中也是很是地多,通常用在短的函數或宏中。
#include <stdio.h> int main() { int a = 1; int b = 2; if (a || ++b) printf("%d/n", b); return 0; }
這個例子,有人會疑問爲何b的值沒有變化,仍是爲2。如今咱們分析下它的彙編代碼
08048374 <main>: 8048374: 8d 4c 24 04 lea 0x4(%esp),%ecx 8048378: 83 e4 f0 and $0xfffffff0,%esp 804837b: ff 71 fc pushl -0x4(%ecx) 804837e: 55 push %ebp 804837f: 89 e5 mov %esp,%ebp //以上彙編碼保存舊棧幀信息,創建新棧幀 8048381: 51 push %ecx //%ecx入棧保護 8048382: 83 ec 24 sub $0x24,%esp //建立本地變量和子函數實參的棧空間(實際上沒所有使用到) 8048385: c7 45 f8 01 00 00 00 movl $0x1,-0x8(%ebp) //變量a賦值,記得本地變量的地址靠近棧幀的基地址,因此用%ebp引用 804838c: c7 45 f4 02 00 00 00 movl $0x2,-0xc(%ebp) //變量b賦值 8048393: 83 7d f8 00 cmpl $0x0,-0x8(%ebp) //變量a和0比較,其實就是判斷「表達式 a」是否是爲假 8048397: 75 0a jne 80483a3 <main+0x2f> //a-0若是不等0,也就是a爲真時就跳到地址80483a3執行。 //已經知道a==1,表達式a爲真,因此將跳到地址80483a3執行 8048399: 83 45 f4 01 addl $0x1,-0xc(%ebp) 804839d: 83 7d f4 00 cmpl $0x0,-0xc(%ebp) 80483a1: 74 13 je 80483b6 <main+0x42> 80483a3: 8b 45 f4 mov -0xc(%ebp),%eax //把變量b的值放到臨時寄存器%eax 80483a6: 89 44 24 04 mov %eax,0x4(%esp) //接着把它做爲printf函數第二個實參入棧, //記得子函數的實參空間靠近棧頂,因此引用實參用%esp 80483aa: c7 04 24 90 84 04 08 movl $0x8048490,(%esp) //printf函數第一個實參入棧。記得X86下用戶層的子函數參數 //是保存到棧的,並且是從右到左依次入棧 80483b1: e8 22 ff ff ff call 80482d8 <printf@plt> //調用printf函數 80483b6: b8 00 00 00 00 mov $0x0,%eax //%eax賦值0,%eax放的也就是main函數返回結果 80483bb: 83 c4 24 add $0x24,%esp //撤銷新棧幀的本地變量棧空間,以及子函數實參棧空間 80483be: 59 pop %ecx //恢復保存的舊%ecx的值 80483bf: 5d pop %ebp //如下彙編碼都是恢復舊棧幀的信息,main函數返回等 80483c0: 8d 61 fc lea -0x4(%ecx),%esp 80483c3: c3 ret
分析可見C語句 if (a || ++b)中的++b對應的彙編碼是
8048399: 83 45 f4 01 addl $0x1,-0xc(%ebp) 804839d: 83 7d f4 00 cmpl $0x0,-0xc(%ebp) 80483a1: 74 13 je 80483b6 <main+0x42>
但是由於a==1,表達式a已經爲真,++b這個語句,也就是上面的彙編碼,根本就沒運行。因此變量b的值沒有自增,仍是保持爲2。
結論
表達式 a, b a || b: 若是a爲真,b就無論;若是運行到b,a必已經是假 a && b: 若是a爲假,b就無論;若是運行到b,a必已經是真
內核代碼實例
static struct char_device_struct * __register_chrdev_region(unsigned int major, unsigned int baseminor, int minorct, const char *name) { ...... i = major_to_index(major); for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next) if ((*cp)->major > major || ((*cp)->major == major && (((*cp)->baseminor >= baseminor) || ((*cp)->baseminor + (*cp)->minorct > baseminor)))) break; ..... }
自增自減
自增自減,以及增減的先後問題。這類代碼在內核數不勝數。理解稍有誤差,就會產生「邊界問題」,或者在條件判斷時理解出錯。
#include <stdio.h> int main() { int i = -1; if (!i++) { printf("inner: %d/n", i); } printf("outer: %d/n", i); return 0; }
彙編代碼
08048374 <main>: 8048374: 8d 4c 24 04 lea 0x4(%esp),%ecx 8048378: 83 e4 f0 and $0xfffffff0,%esp 804837b: ff 71 fc pushl -0x4(%ecx) 804837e: 55 push %ebp 804837f: 89 e5 mov %esp,%ebp 8048381: 51 push %ecx 8048382: 83 ec 24 sub $0x24,%esp 8048385: c7 45 f8 ff ff ff ff movl $0xffffffff,-0x8(%ebp) 804838c: 83 45 f8 01 addl $0x1,-0x8(%ebp) 8048390: 83 7d f8 01 cmpl $0x1,-0x8(%ebp) 8048394: 75 13 jne 80483a9 <main+0x35> 8048396: 8b 45 f8 mov -0x8(%ebp),%eax 8048399: 89 44 24 04 mov %eax,0x4(%esp) 804839d: c7 04 24 90 84 04 08 movl $0x8048490,(%esp) 80483a4: e8 2f ff ff ff call 80482d8 <printf@plt> 80483a9: 8b 45 f8 mov -0x8(%ebp),%eax 80483ac: 89 44 24 04 mov %eax,0x4(%esp) 80483b0: c7 04 24 9b 84 04 08 movl $0x804849b,(%esp) 80483b7: e8 1c ff ff ff call 80482d8 <printf@plt> 80483bc: b8 00 00 00 00 mov $0x0,%eax 80483c1: 83 c4 24 add $0x24,%esp 80483c4: 59 pop %ecx 80483c5: 5d pop %ebp 80483c6: 8d 61 fc lea -0x4(%ecx),%esp 80483c9: c3 ret 80483ca: 90 nop
內核代碼實例
int platform_add_devices(struct platform_device **devs, int num) { int i, ret = 0; for (i = 0; i < num; i++) { ret = platform_device_register(devs[i]); if (ret) { while (--i >= 0) /*沒錯,devs[i]沒註冊成功的話,從devs[i-1]起反註冊*/ platform_device_unregister(devs[i]); break; } } return ret; }
函數指針
解釋在「穿越交叉索引工具的盲區」→函數指針
#include <stdio.h> int main() { int myfunc(int a, int b) { int c = a + b; printf("%d/n", c); return 0; } int (*funa)(int, int) = myfunc; int (*funb)(int, int) = &myfunc; int (*func)(int, int) = (int (*)(int, int))myfunc; int (*fund)(int, int) = (int (*)(int, int))(&myfunc); myfunc(1, 2); funa(3, 4); funb(5, 6); func(7, 8); fund(9, 10); return 0; } 編譯: $ gcc -g -Wall fuk.c //注意,沒任何警告 int main() { 8048374: 8d 4c 24 04 lea 0x4(%esp),%ecx ....省略 int (*funa)(int, int) = myfunc; 8048385: c7 45 f8 13 84 04 08 movl $0x8048413,-0x8(%ebp) int (*funb)(int, int) = &myfunc; 804838c: c7 45 f4 13 84 04 08 movl $0x8048413,-0xc(%ebp) int (*func)(int, int) = (int (*)(int, int))myfunc; 8048393: c7 45 f0 13 84 04 08 movl $0x8048413,-0x10(%ebp) int (*fund)(int, int) = (int (*)(int, int))(&myfunc); 804839a: c7 45 ec 13 84 04 08 movl $0x8048413,-0x14(%ebp) myfunc(1, 2); ...省略 funa(3, 4); 80483b5: c7 44 24 04 04 00 00 movl $0x4,0x4(%esp) 80483bc: 00 80483bd: c7 04 24 03 00 00 00 movl $0x3,(%esp) 80483c4: 8b 45 f8 mov -0x8(%ebp),%eax 80483c7: ff d0 call *%eax funb(5, 6); ....省略,funb, func,fund彙編碼和funa徹底相同 return 0; 8048405: b8 00 00 00 00 mov $0x0,%eax } 804840a: 83 c4 24 add $0x24,%esp ...省略 08048413 <myfunc.1933>: #include <stdio.h> int main() { int myfunc(int a, int b) { 8048413: 55 push %ebp .....省略 } xxx@ubuntu:~/dt/test$ gdb a.out GNU gdb 6.8-debian ... (gdb) list 1 #include <stdio.h> ...... 17 funa(3, 4); .... 20 (gdb) b 17 (gdb) r Starting program: /home/xxx/桌面/test/a.out Breakpoint 1, main () at fuck.c:17 17 funa(3, 4); (gdb) display/i $pc 1: x/i $pc 0x80483b5 <main+65>: movl $0x4,0x4(%esp) (gdb) stepi 0x080483bd 17 funa(3, 4); 1: x/i $pc 0x80483bd <main+73>: movl $0x3,(%esp) (gdb) 0x080483c4 17 funa(3, 4); 1: x/i $pc 0x80483c4 <main+80>: mov -0x8(%ebp),%eax (gdb) 0x080483c7 17 funa(3, 4); 1: x/i $pc 0x80483c7 <main+83>: call *%eax (gdb) p/x $eax $4 = 0x8048413 (gdb) info line *0x8048413 Line 6 of "fuck.c" starts at address 0x8048413 <myfunc> and ends at 0x8048419 <myfunc+6>. (gdb)
這部份內容有點偏題,不必這麼鑽牛角尖。可是爲了說明「調試用的代碼和實際運行的代碼是不同」的這個事實以及由於代碼優化致使的「非理想狀態」的調用鏈問題(見「內核初窺」),有必要用觀察一個實例,以便有個直觀的印象。
首先應該知道,有沒有指定調試選項-g(–debug),在相同優先級下生成的代碼都是同樣的。差異只是,指定-g後,多生成了一個調試表。
下面文字來自「ARM 系列應用技術徹底手冊」
使用-Onum選擇編譯器的優化級別。優化級別分別有
#include <stdio.h> int add(int a, int b) { return (a + b); } void funa() { int a = 3 + 4; int b; printf("%d/n", a); b = add(5,6); printf("%d/n", b); } int main() { int m = 1 + 2; printf("%d/n", m); funa(); }
$ gcc -g -O0 src.c (或者不指定優化選項: gcc -g src.c,編譯出的機器碼同樣) $ objdump -d a.out 獲得一個結論:若是指定了-g而沒指定優化等級,那麼默認優化等級是最低的-O0 08048374 <add>: 8048374: 55 push %ebp 8048375: 89 e5 mov %esp,%ebp 8048377: 8b 45 0c mov 0xc(%ebp),%eax 804837a: 03 45 08 add 0x8(%ebp),%eax 804837d: 5d pop %ebp 804837e: c3 ret 0804837f <funa>: 804837f: 55 push %ebp 8048380: 89 e5 mov %esp,%ebp //保存舊棧幀,創建新棧幀 8048382: 83 ec 18 sub $0x18,%esp //分配棧幀空間,注意分配了$0x18 8048385: c7 45 fc 07 00 00 00 movl $0x7,-0x4(%ebp) //-0x4(%ebp)是本地變量a的地址,int a = 3 + 4; //注意編譯器已經完成了計算 804838c: 8b 45 fc mov -0x4(%ebp),%eax //a放到臨時寄存器%eax 804838f: 89 44 24 04 mov %eax,0x4(%esp) //接着做爲printf第二個參數入棧 8048393: c7 04 24 d0 84 04 08 movl $0x80484d0,(%esp) //printf第一個參數入棧 804839a: e8 39 ff ff ff call 80482d8 <printf@plt> //printf("%d/n", a); 804839f: c7 44 24 04 06 00 00 movl $0x6,0x4(%esp) //add(5,6);第二個參數入棧 80483a6: 00 80483a7: c7 04 24 05 00 00 00 movl $0x5,(%esp) //add(5,6);第一個參數入棧 80483ae: e8 c1 ff ff ff call 8048374 <add> //調用add 80483b3: 89 45 f8 mov %eax,-0x8(%ebp) //-0x8(%ebp)是本地變量b的地址,b = add(5,6); 80483b6: 8b 45 f8 mov -0x8(%ebp),%eax //b放到臨時寄存器%eax 80483b9: 89 44 24 04 mov %eax,0x4(%esp) //接着做爲printf第二個參數入棧 80483bd: c7 04 24 d0 84 04 08 movl $0x80484d0,(%esp) //printf第一個參數入棧 80483c4: e8 0f ff ff ff call 80482d8 <printf@plt> //printf("%d/n", b); 80483c9: c9 leave //撤銷新棧幀空間 80483ca: c3 ret //funa返回 080483cb <main>: 80483cb: 8d 4c 24 04 lea 0x4(%esp),%ecx 80483cf: 83 e4 f0 and $0xfffffff0,%esp 80483d2: ff 71 fc pushl -0x4(%ecx) 80483d5: 55 push %ebp 80483d6: 89 e5 mov %esp,%ebp 80483d8: 51 push %ecx 80483d9: 83 ec 24 sub $0x24,%esp 80483dc: c7 45 f8 03 00 00 00 movl $0x3,-0x8(%ebp) 80483e3: 8b 45 f8 mov -0x8(%ebp),%eax 80483e6: 89 44 24 04 mov %eax,0x4(%esp) 80483ea: c7 04 24 d0 84 04 08 movl $0x80484d0,(%esp) 80483f1: e8 e2 fe ff ff call 80482d8 <printf@plt> 80483f6: e8 84 ff ff ff call 804837f <funa> 80483fb: 83 c4 24 add $0x24,%esp 80483fe: 59 pop %ecx 80483ff: 5d pop %ebp 8048400: 8d 61 fc lea -0x4(%ecx),%esp 8048403: c3 ret
$ gcc -g -O1 src.c $ objdump -d a.out 08048374 <add>: 8048374: 55 push %ebp 8048375: 89 e5 mov %esp,%ebp 8048377: 8b 45 0c mov 0xc(%ebp),%eax 804837a: 03 45 08 add 0x8(%ebp),%eax 804837d: 5d pop %ebp 804837e: c3 ret 0804837f <funa>: //funa與-O0相比,沒有了向本地變量a,b賦值的過程。 //代碼量少了,分配的棧幀空間也小了。 804837f: 55 push %ebp 8048380: 89 e5 mov %esp,%ebp 8048382: 83 ec 08 sub $0x8,%esp //分配棧幀空間,注意分配了$0x8,比-O0下小了 8048385: c7 44 24 04 07 00 00 movl $0x7,0x4(%esp) //printf("%d/n", a);的第二個參數入棧。 //注意,與-O0相比,沒有向本地變量a賦值的過程。 804838c: 00 804838d: c7 04 24 c0 84 04 08 movl $0x80484c0,(%esp) 8048394: e8 3f ff ff ff call 80482d8 <printf@plt> //printf("%d/n", a); 8048399: c7 44 24 04 06 00 00 movl $0x6,0x4(%esp) 80483a0: 00 80483a1: c7 04 24 05 00 00 00 movl $0x5,(%esp) 80483a8: e8 c7 ff ff ff call 8048374 <add> //add(5,6); 80483ad: 89 44 24 04 mov %eax,0x4(%esp) //add的返回結果做爲printf("%d/n", b);的第二個參數入棧。 //注意,與-O0相比,沒有向本地變量b賦值的過程。 80483b1: c7 04 24 c0 84 04 08 movl $0x80484c0,(%esp) 80483b8: e8 1b ff ff ff call 80482d8 <printf@plt> //printf("%d/n", b); 80483bd: c9 leave 80483be: c3 ret 080483bf <main>: 80483bf: 8d 4c 24 04 lea 0x4(%esp),%ecx 80483c3: 83 e4 f0 and $0xfffffff0,%esp 80483c6: ff 71 fc pushl -0x4(%ecx) 80483c9: 55 push %ebp 80483ca: 89 e5 mov %esp,%ebp 80483cc: 51 push %ecx 80483cd: 83 ec 14 sub $0x14,%esp 80483d0: c7 44 24 04 03 00 00 movl $0x3,0x4(%esp) 80483d7: 00 80483d8: c7 04 24 c0 84 04 08 movl $0x80484c0,(%esp) 80483df: e8 f4 fe ff ff call 80482d8 <printf@plt> 80483e4: e8 96 ff ff ff call 804837f <funa> 80483e9: 83 c4 14 add $0x14,%esp 80483ec: 59 pop %ecx 80483ed: 5d pop %ebp 80483ee: 8d 61 fc lea -0x4(%ecx),%esp 80483f1: c3 ret
$ gcc -g -O2 src.c $ objdump -d a.out 咱們應該知道,若是沒有指定-g和優化選項,那麼默認的優化等級就是-O2 08048380 <add>: 8048380: 55 push %ebp 8048381: 89 e5 mov %esp,%ebp 8048383: 8b 45 0c mov 0xc(%ebp),%eax 8048386: 03 45 08 add 0x8(%ebp),%eax 8048389: 5d pop %ebp 804838a: c3 ret 804838b: 90 nop 804838c: 8d 74 26 00 lea 0x0(%esi),%esi 08048390 <funa>: 8048390: 55 push %ebp 8048391: 89 e5 mov %esp,%ebp 8048393: 83 ec 08 sub $0x8,%esp 8048396: c7 44 24 04 07 00 00 movl $0x7,0x4(%esp) 804839d: 00 804839e: c7 04 24 d0 84 04 08 movl $0x80484d0,(%esp) 80483a5: e8 2e ff ff ff call 80482d8 <printf@plt> 80483aa: c7 44 24 04 06 00 00 movl $0x6,0x4(%esp) 80483b1: 00 80483b2: c7 04 24 05 00 00 00 movl $0x5,(%esp) 80483b9: e8 c2 ff ff ff call 8048380 <add> 80483be: c7 04 24 d0 84 04 08 movl $0x80484d0,(%esp) //第二個參數入棧 80483c5: 89 44 24 04 mov %eax,0x4(%esp) //第一個參數入棧。注意和-O1相比,參數在棧幀空間的位置沒變, //可是入棧指令的執行順序有變。 80483c9: e8 0a ff ff ff call 80482d8 <printf@plt> //printf("%d/n", b); 80483ce: c9 leave 80483cf: c3 ret 080483d0 <main>: 80483d0: 8d 4c 24 04 lea 0x4(%esp),%ecx 80483d4: 83 e4 f0 and $0xfffffff0,%esp 80483d7: ff 71 fc pushl -0x4(%ecx) 80483da: 55 push %ebp 80483db: 89 e5 mov %esp,%ebp 80483dd: 51 push %ecx 80483de: 83 ec 14 sub $0x14,%esp 80483e1: c7 44 24 04 03 00 00 movl $0x3,0x4(%esp) 80483e8: 00 80483e9: c7 04 24 d0 84 04 08 movl $0x80484d0,(%esp) 80483f0: e8 e3 fe ff ff call 80482d8 <printf@plt> 80483f5: e8 96 ff ff ff call 8048390 <funa> 80483fa: 83 c4 14 add $0x14,%esp 80483fd: 59 pop %ecx 80483fe: 5d pop %ebp 80483ff: 8d 61 fc lea -0x4(%ecx),%esp 8048402: c3 ret
$ gcc -g -O3 src.c $ objdump -d a.out 048380 <add>: 8048380: 55 push %ebp 8048381: 89 e5 mov %esp,%ebp 8048383: 8b 45 0c mov 0xc(%ebp),%eax 8048386: 03 45 08 add 0x8(%ebp),%eax 8048389: 5d pop %ebp 804838a: c3 ret 804838b: 90 nop 804838c: 8d 74 26 00 lea 0x0(%esi),%esi 08048390 <funa>: //與-O2相比,對函數add()的調用被編譯器優化消失 8048390: 55 push %ebp 8048391: 89 e5 mov %esp,%ebp 8048393: 83 ec 08 sub $0x8,%esp 8048396: c7 44 24 04 07 00 00 movl $0x7,0x4(%esp) 804839d: 00 804839e: c7 04 24 e0 84 04 08 movl $0x80484e0,(%esp) 80483a5: e8 2e ff ff ff call 80482d8 <printf@plt> 80483aa: c7 44 24 04 0b 00 00 movl $0xb,0x4(%esp) //注意,與-O2相比,b = add(5,6);被優化掉了。 //以前應該有個優化爲內聯函數的過程,但由於add函數 //太簡單,被直接計算告終果。(猜測) //編譯器直接計算出它的結果$0xb,也就是11 80483b1: 00 80483b2: c7 04 24 e0 84 04 08 movl $0x80484e0,(%esp) 80483b9: e8 1a ff ff ff call 80482d8 <printf@plt> //printf("%d/n", b); 80483be: c9 leave 80483bf: c3 ret 080483c0 <main>: 80483c0: 8d 4c 24 04 lea 0x4(%esp),%ecx 80483c4: 83 e4 f0 and $0xfffffff0,%esp 80483c7: ff 71 fc pushl -0x4(%ecx) 80483ca: 55 push %ebp 80483cb: 89 e5 mov %esp,%ebp 80483cd: 51 push %ecx 80483ce: 83 ec 14 sub $0x14,%esp 80483d1: c7 44 24 04 03 00 00 movl $0x3,0x4(%esp) 80483d8: 00 80483d9: c7 04 24 e0 84 04 08 movl $0x80484e0,(%esp) 80483e0: e8 f3 fe ff ff call 80482d8 <printf@plt> 80483e5: c7 44 24 04 07 00 00 movl $0x7,0x4(%esp) 80483ec: 00 80483ed: c7 04 24 e0 84 04 08 movl $0x80484e0,(%esp) 80483f4: e8 df fe ff ff call 80482d8 <printf@plt> 80483f9: c7 44 24 04 0b 00 00 movl $0xb,0x4(%esp) 8048400: 00 8048401: c7 04 24 e0 84 04 08 movl $0x80484e0,(%esp) 8048408: e8 cb fe ff ff call 80482d8 <printf@plt> 804840d: 83 c4 14 add $0x14,%esp 8048410: 59 pop %ecx 8048411: 5d pop %ebp 8048412: 8d 61 fc lea -0x4(%ecx),%esp 8048415: c3 ret
說明:
1. 部份內容和X86的重複,重複部分請參考X86的內容。
2. 某些內容不具有廣泛性。好比給出的反彙編代碼,在不一樣的優化等級下是不一樣的。可是在熟悉了典型的函數調用鏈反彙編代碼,對於有變化的其餘形式也就不難理解了。
ARM7TDMI Technical Reference Manual
ARM920T Technical Reference Manual
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.home/index.html
注意:arm體系過程調用的文字說明部分,都是依據AAPCS標準。
參考:
AAPCS
Procedure Call Standard for the ARM Architecture
http://infocenter.arm.com/help/topic/com.arm.doc.ihi0042b/IHI0042B_aapcs.pdf
終於在「ARM Procedure Call Standard」中找到了答案
PCS Procedure Call Standard. AAPCS Procedure Call Standard for the ARM Architecture (this standard). APCS ARM Procedure Call Standard (obsolete). TPCS Thumb Procedure Call Standard (obsolete). ATPCS ARM-Thumb Procedure Call Standard (precursor to this standard). PIC, PID Position-independent code, position-independent data.
下面的標準已過期
APCS
ARM Procedure Call Standard http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0041c/BGBGFIDA.html
Using the ARM Procedure Call Standard http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0040d/Chdbceig.html
APCS 簡介http://www.bsdmap.com/UNIX_html/ARM/apcsintro.html#01
TPCS
Thumb Procedure Call Standard http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0041c/BCEEAHAF.html
Using the Thumb Procedure Call Standard http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0040d/Cihdbchi.html
ATPCS
About the ARM-Thumb Procedure Call Standard http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0056d/Bcffcieh.html
arm體系的函數調用標準換了好幾個版本,對寄存器的別名也是不同。不一樣的調試器,或者它在不一樣的選項下,對同一個寄存器可能就有多種稱呼。又或者你在調試器下看到的名稱和書籍上的不同。因此,又必要知道這些寄存器各自都有哪些別名。
咱們運行下命令
$ arm-linux-gnueabi-objdump --help ....省略 The following ARM specific disassembler options are supported for use with the -M switch: reg-names-special-atpcs Select special register names used in the ATPCS reg-names-atpcs Select register names used in the ATPCS reg-names-apcs Select register names used in the APCS reg-names-std Select register names used in ARM's ISA documentation reg-names-gcc Select register names used by GCC reg-names-raw Select raw register names force-thumb Assume all insns are Thumb insns no-force-thumb Examine preceeding label to determine an insn's type
咱們下載它的源碼打開看看
$ sudo apt-get source binutils-arm-linux-gnueabi
完成後,在下載目錄下多了幾個東東,其中有一個文件夾binutils-2.18.1~cvs20080103,這是debian對官方binutils進行過修改的源碼。在裏面搜索文件arm-dis.c,該文件中有如下這個數組。
就是不一樣標準下各個寄存器的不一樣別名。
static const arm_regname regnames[] = { { "raw" , "Select raw register names", { "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15"}}, { "gcc", "Select register names used by GCC", { "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "sl", "fp", "ip", "sp", "lr", "pc" }}, { "std", "Select register names used in ARM's ISA documentation", { "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "sp", "lr", "pc" }}, { "apcs", "Select register names used in the APCS", { "a1", "a2", "a3", "a4", "v1", "v2", "v3", "v4", "v5", "v6", "sl", "fp", "ip", "sp", "lr", "pc" }}, { "atpcs", "Select register names used in the ATPCS", { "a1", "a2", "a3", "a4", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v8", "IP", "SP", "LR", "PC" }}, { "special-atpcs", "Select special register names used in the ATPCS", { "a1", "a2", "a3", "a4", "v1", "v2", "v3", "WR", "v5", "SB", "SL", "FP", "IP", "SP", "LR", "PC" }}, }; 可是能夠看到,該列表並無包含AAPCS標準,AAPCS標準對 r9 又引入了一個別名 TR,這樣AAPCS下,r9使用了三個別名v6, SB, TR。選用哪一個 別名,是依賴於不一樣平臺的選擇。
[擴展,簡要說明原理。並用實例解析]
寄存器 | 可選寄存器名 | 特殊寄存器名 | 在函數調用中的角色 |
---|---|---|---|
r15 | PC | The Program Counter. | |
r14 | LR | The Link Register. | |
r13 | SP | The Stack Pointer. | |
r12 | IP | The Intra-Procedure-call scratch register. | |
r11 | v8 | Variable-register 8. | |
r10 | v7 | Variable-register 7. | |
r9 | v6/SB/TR | Platform register. The meaning of this register is defined by the platform standard | |
r8 | v5 | Variable-register 5. | |
r7 | v4 | Variable register 4. | |
r6 | v3 | Variable register 3. | |
r5 | v2 | Variable register 2. | |
r4 | v1 | Variable register 1. | |
r3 | a4 | Argument / scratch register 4. | |
r2 | a3 | Argument / scratch register 3. | |
r1 | a2 | Argument / result / scratch register 2. | |
r0 | a1 | Argument / result / scratch register 1. |
前四個寄存器r0-r3 (a1-a4)用於傳遞參數給子函數或從函數中返回結果值。他們也可用於在一個函數中保存寄存器的值(可是,通常只用在子函數調用中)。
寄存器r12 (IP) 可在函數以及該函數調用的任何子函數中被連接器用做臨時寄存器。它也能夠在函數調用中用於保存寄存器的值。
寄存器r9的角色是平臺相關的。虛擬系統可能賦予該寄存器任何角色,所以必須說明它的用法。好比,在位置無關數據模型中它能夠指定爲static base(SB),或者在帶有本地線程存儲的環境中指定它爲thread register(TR)。該寄存器的使用可能要求在全部調用過程先後,它保存的值必須不變。在一個不須要這樣特殊寄存器的虛擬平臺上,r9能夠指定爲新增的callee-saved variable register,v6.
一般,寄存器r4-r8, r10 和 r11 (v1-v5, v7 和 v8)用於保存函數的本地變量。這些寄存器中,只有v1-v4能被整個thumb指令集一致地使用,可是AAPCS並無規定Thumb代碼只能使用這些寄存器。
子函數必須保護寄存器r4-r8, r10, r11 和 SP(還有r9,若是在函數調用過程當中r6被指定爲v6的話)的值。
在全部的函數調用標準中,寄存器r12-r15都扮演特殊的角色。依據這些角色,它們被標註爲IP, SP, LR 和 PC。
寄存器CPSR的屬性(省)
子函數必須保護寄存器r4-r8, r10, r11 和 SP(還有r9,若是在函數調用過程當中r6被指定爲v6的話)的值。 子函數調用
ARM 和 Thumb 指令集都有一個函數調用指令元語,BL,它執行branch-with-link 操做。BL的執行效果是把緊跟程序計數器的下一個值--也就是返回地址--傳送到連接寄存器(LR),而後把目標地址傳送到程序寄存器(PC)中。若是BL指令是在Thumb狀態下執行的,連接寄存器的Bit 0就設置爲1;若是是在ARM狀態下執行的,則設置爲0。執行的結果是,把控制權轉給目標地址,並把存放在LR中的返回地址做爲附加的參數傳遞給了被調用的函數。
當返回地址裝載到PC時,控制就返回給跟隨BL後面的指令。
子函數調用能夠由具備下面效果的任何指令序列完成:
LR[31:1] ← 返回地址 LR[0] ← 返回地址的代碼類型 (0 ARM, 1 Thumb) PC ← 子函數地址 ... 返回地址:
例如,在ARM狀態中,調用由r4指定了地址的子函數
do: MOV LR, PC BX r4 ...
注意,相同的指令序列在Thumb狀態中將不能工做,由於設置LR的指令並無拷貝Thumb 狀態標誌位到LR[0]中。
在ARM V5架構中,ARM 和 Thumb指令集都提供了BLX指令,它將調用由一個寄存器指定了地址的子函數,並正確地設置返回地址爲程序計數器的下一個值。
操做碼[31:28] | 助記符擴展 | 解釋 | 用於執行的標誌位狀態 |
---|---|---|---|
0000 | EQ | 相等/等於0 | Z置位 |
0001 | NE | 不等 | Z清0 |
0010 | CS/HS | 進位/無符號數高於或等於 | C置位 |
0011 | CC/LO | 無進位/無符號數小於 | C清0 |
0100 | MI | 負數 | N置位 |
0101 | PL | 正數或0 | N清0 |
0110 | VS | 溢出 | V置位 |
0111 | VC | 未溢出 | V清0 |
1000 | HI | 無符號數高於 | C置位,Z清0 |
1001 | LS | 無符號數小於或等於 | C清0,Z置位 |
1010 | GE | 有符號數大於或等於 | N等於V |
1011 | LT | 有符號數小於 | N不等於V |
1100 | GT | 有符號數大於 | Z清0且N等於V |
1101 | LE | 有符號數小於或等於 | Z置位且N不等於V |
1110 | AL | 老是 | 任何狀態 |
1111 | NV | 從不(未使用) | 無 |
注意對比ARM和X86在調用鏈造成的相似和區別之處。
區別,首先在寄存器的名稱和角色的差別。
1. X86中寄存器%eip指向的是下一個將要執行的指令。在ARM中也有個相似別名的寄存器ip。但這個寄存器ip的做用並非指向的是下一個將要執行的指令。在ARM中,寄存器pc纔是起着X86中寄存器%eip的角色,也就是包含下一個將要執行指令的地址。而ARM中的ip寄存器,做用比較自由,相似幹雜工的人,通常用於臨時寄存器。[擴展,引用權威手冊的話]
2. X86中,返回地址是直接保存在棧中的。可是ARM不同了,它寄存器比X86多得多,財大氣粗,因此,返回地址保存在了專用的寄存器lr(link register)中。可是,不要覺得把返回地址放到專用的寄存器中會省事,其實反而多事了。由於,在調用函數剛執行完調用語句之時,lr保存的是子函數的返回地址,而指令控制權轉移到了子函數後,子函數照樣可能調用本身的子函數,依次須要使用lr。因此天然也就有了lr的值的保存與恢復的問題,解決方法仍是要靠壓棧解決。(參考下面的內容)
3. 咱們知道,描述棧幀就是描述棧幀的基地址和頂端地址。在X86中,用專用的寄存器%ebp保存棧基址,也就是base pointer;%esp保存棧頂端地址,也就是stack pointer。在ARM中,也有專用的寄存器保存棧頂端地址,就是SP(stack pointer的簡稱)。可是,在保存棧基址這方面,依據最新的AAPCS標準,ARM就很吝嗇了,沒有一個保存棧基址的專用寄存器。又不過呢,在APCS和ATPCS標準中,有fp寄存器用於保存幀指針(frame pointer,也就是X86的base pointer)。在如今的編譯器,能夠看到,仍是依照慣例把fp用於保存幀指針。既然如此,固然也有個入棧保存恢復的問題。
調用鏈包含兩方面的內容,和X86相似
1.返回地址的保存與恢復
由調用函數在執行調用指令時把子函數的返回地址傳送進鏈接寄存器lr中,指令控制權轉交給子函數後,再由子函數負責把上層函數的lr(也就是子函數的返回地址)保存到棧中。而後子函數在返回前的最後時刻,再負責把lr的保存值從棧彈回到lr中,從而恢復了上層函數的lr。這時還沒完事,子函數在執行返回指令時,由返回指令把lr的值傳送到寄存器pc(Program Counter),從而致使接下來的指令是從子函數的返回地址開始運行。這樣,指令控制權就返回給了調用函數。
咱們應當注意到,ARM中調用指令也是多種多樣的。有b,bl,bx,bxl。若是調用指令是不帶鏈接的指令,好比b,bx,這時就要人工給lr賦值。不過爲了簡便,我再也不區分這兩類指令,而把實現跳轉和鏈接以及可能的換態這些功能的整個指令序列爲「調用指令」,相關區別參考指令手冊。在ARM中,返回指令和調用指令都是同一套的。而X86,調用用call,返回用ret。
2.舊棧幀的保存與恢復
對比X86棧幀的保存與恢復的方式,ARM的更加簡單直接。就是直接把上一棧幀的幀指針(frame pointer,也就是棧幀基地址)以及棧頂端指針sp(stack pointer)壓入棧中。子函數返回時,在執行返回指令以前的最後關頭才從棧彈出fp和sp的值,從而恢復舊棧幀。這個過程真的沒有遺漏了嗎?咱們看下,上面的步驟保證了調用函數的棧幀不被破壞,可是子函數本身的棧幀卻沒有創建起來呢。首先是幀指針須要人賦值。這個情形和X86很是類似。子函數在使用棧幀以前,把上層函數的棧頂端指針sp賦給一個臨時寄存器ip,而後在舊fp的值被壓棧保存以後,把ip的值減去4,再賦給幀指針寄存器fp,此時,fp就指向了新棧幀的基址。這是由於,新棧幀基地址恰好位於舊棧幀棧頂之下,地址低了4字節。其次,子函數棧幀的棧頂指針sp也是要考慮的,根據壓棧指令的不一樣,sp可能不須要人工維護,也可能須要人工維護[有疑問...????]。
咱們還注意到,在X86中,子函數的棧幀的底端(也就是%ebp所指的內存位置)存放着上一層棧幀的基址指針(舊%ebp)的值,一層層下去,這樣就造成回溯的鏈條。那麼,在ARM之下,也是靠子函數的棧幀的底端提供回溯的能力的嗎?固然不是。實際上子函數的棧幀的基址位置存放的是什麼,這無所謂的。
[疑問???若是舊fp保存在新棧幀中的位置不是固定的,那麼調試器是如何作到棧幀回溯的呢?]
根據AAPCS標準的規定,子函數必須保護寄存器r4-r8, r10, r11 和 SP(還有r9,若是在函數調用過程當中r6被指定爲v6的話)的值。注意,它用的字眼是「保護」,而不是「保存」。
#include <stdio.h> void func() {} void funb() { func(); } void funa() { funb(); } int main() { funa(); } ----------- 000083b0 <func>: #include <stdio.h> void func() {} 83b0: e1a0c00d mov ip, sp 83b4: e92dd800 push {fp, ip, lr, pc} 83b8: e24cb004 sub fp, ip, #4 ; 0x4 83bc: e24bd00c sub sp, fp, #12 ; 0xc 83c0: e89d6800 ldm sp, {fp, sp, lr} 83c4: e12fff1e bx lr 000083c8 <funb>: void funb() { 83c8: e1a0c00d mov ip, sp 83cc: e92dd800 push {fp, ip, lr, pc} 83d0: e24cb004 sub fp, ip, #4 ; 0x4 func(); 83d4: ebfffff5 bl 83b0 <func> } 83d8: e24bd00c sub sp, fp, #12 ; 0xc 83dc: e89d6800 ldm sp, {fp, sp, lr} 83e0: e12fff1e bx lr 000083e4 <funa>: void funa() { 83e4: e1a0c00d mov ip, sp 83e8: e92dd800 push {fp, ip, lr, pc} 83ec: e24cb004 sub fp, ip, #4 ; 0x4 funb(); 83f0: ebfffff4 bl 83c8 <funb> } 83f4: e24bd00c sub sp, fp, #12 ; 0xc 83f8: e89d6800 ldm sp, {fp, sp, lr} 83fc: e12fff1e bx lr 00008400 <main>: int main() { 8400: e1a0c00d mov ip, sp 8404: e92dd800 push {fp, ip, lr, pc} 8408: e24cb004 sub fp, ip, #4 ; 0x4 funa(); 840c: ebfffff4 bl 83e4 <funa> } 8410: e24bd00c sub sp, fp, #12 ; 0xc 8414: e89d6800 ldm sp, {fp, sp, lr} 8418: e12fff1e bx lr
[1.棧:棧對齊,棧限制。2.參數傳遞:variadic函數,nonvariadic函數。3.結果的返回 4.互交代碼(ARM-Thumb interworking)]
棧幀示意圖
+------------------------------ + --------- | Register Save Area | | +------------------------------ + | | Locals and Temporaries | | +------------------------------ + | alloca() Locals | Caller's Frame +------------------------------ + | Incoming Args Past Four Words | | +------------------------------ + --------- | First Four Words Of Args | | Frame Pointer--> +------------------------------ + | | Register Save Area | | +------------------------------ + Current Frame | Locals and Temporaries | +------------------------------ + | | alloca() Locals | | +------------------------------ + | | Outgoing Args Past Four Words | | Stack Pointer---> +------------------------------ + ---------
函數caller調用子函數callee,這是應用層的普通函數調用過程。若是是遠調用,跨態調用要考慮的東西更多。但這個例子已經充分展現了調用過程的繁複部分。
arm體系對調用鏈的回溯的代碼實現主要在
arch/arm/kernel/traps.c 和arch/arm/lib/backtrace.S.其中核心函數是backtrace.S中的__backtrace函數。 待解釋 ---/* * linux/arch/arm/lib/backtrace.S * * Copyright (C) 1995, 1996 Russell King * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * 27/03/03 Ian Molton Clean up CONFIG_CPU * */ #include <linux/linkage.h> #include <asm/assembler.h> .text @ fp is 0 or stack frame #define frame r4 #define sv_fp r5 #define sv_pc r6 #define mask r7 #define offset r8 ENTRY(__backtrace) mov r1, #0x10 mov r0, fp ENTRY(c_backtrace) #if !defined(CONFIG_FRAME_POINTER) || !defined(CONFIG_PRINTK) mov pc, lr ENDPROC(__backtrace) ENDPROC(c_backtrace) #else stmfd sp!, {r4 - r8, lr} @ Save an extra register so we have a location... movs frame, r0 @ if frame pointer is zero beq no_frame @ we have no stack frames tst r1, #0x10 @ 26 or 32-bit mode? moveq mask, #0xfc000003 @ mask for 26-bit movne mask, #0 @ mask for 32-bit 1: stmfd sp!, {pc} @ calculate offset of PC stored ldr r0, [sp], #4 @ by stmfd for this CPU adr r1, 1b sub offset, r0, r1 /* * Stack frame layout: * optionally saved caller registers (r4 - r10) * saved fp * saved sp * saved lr * frame => saved pc * optionally saved arguments (r0 - r3) * saved sp => <next word> * * Functions start with the following code sequence: * mov ip, sp * stmfd sp!, {r0 - r3} (optional) * corrected pc => stmfd sp!, {..., fp, ip, lr, pc} */ for_each_frame: tst frame, mask @ Check for address exceptions bne no_frame 1001: ldr sv_pc, [frame, #0] @ get saved pc 1002: ldr sv_fp, [frame, #-12] @ get saved fp sub sv_pc, sv_pc, offset @ Correct PC for prefetching bic sv_pc, sv_pc, mask @ mask PC/LR for the mode 1003: ldr r2, [sv_pc, #-4] @ if stmfd sp!, {args} exists, ldr r3, .Ldsi+4 @ adjust saved 'pc' back one teq r3, r2, lsr #10 @ instruction subne r0, sv_pc, #4 @ allow for mov subeq r0, sv_pc, #8 @ allow for mov + stmia ldr r1, [frame, #-4] @ get saved lr mov r2, frame bic r1, r1, mask @ mask PC/LR for the mode bl dump_backtrace_entry ldr r1, [sv_pc, #-4] @ if stmfd sp!, {args} exists, ldr r3, .Ldsi+4 teq r3, r1, lsr #10 ldreq r0, [frame, #-8] @ get sp subeq r0, r0, #4 @ point at the last arg bleq .Ldumpstm @ dump saved registers 1004: ldr r1, [sv_pc, #0] @ if stmfd sp!, {..., fp, ip, lr, pc} ldr r3, .Ldsi @ instruction exists, teq r3, r1, lsr #10 subeq r0, frame, #16 bleq .Ldumpstm @ dump saved registers teq sv_fp, #0 @ zero saved fp means beq no_frame @ no further frames cmp sv_fp, frame @ next frame must be mov frame, sv_fp @ above the current frame bhi for_each_frame 1006: adr r0, .Lbad mov r1, frame bl printk no_frame: ldmfd sp!, {r4 - r8, pc} ENDPROC(__backtrace) ENDPROC(c_backtrace) .section __ex_table,"a" .align 3 .long 1001b, 1006b .long 1002b, 1006b .long 1003b, 1006b .long 1004b, 1006b .previous #define instr r4 #define reg r5 #define stack r6 .Ldumpstm: stmfd sp!, {instr, reg, stack, r7, lr} mov stack, r0 mov instr, r1 mov reg, #10 mov r7, #0 1: mov r3, #1 tst instr, r3, lsl reg beq 2f add r7, r7, #1 teq r7, #6 moveq r7, #1 moveq r1, #'/n' movne r1, #' ' ldr r3, [stack], #-4 mov r2, reg adr r0, .Lfp bl printk 2: subs reg, reg, #1 bpl 1b teq r7, #0 adrne r0, .Lcr blne printk ldmfd sp!, {instr, reg, stack, r7, pc} .Lfp: .asciz "%cr%d:%08x" .Lcr: .asciz "/n" .Lbad: .asciz "Backtrace aborted due to bad frame pointer <%p>/n" .align .Ldsi: .word 0xe92dd800 >> 10 @ stmfd sp!, {... fp, ip, lr, pc} .word 0xe92d0000 >> 10 @ stmfd sp!, {} #endif
本節意義: 內核源碼的代碼量愈來愈大,不借助源碼交叉索引工具根本是沒法閱讀了。必定要熟練靈活掌握此類工具的使用
1.CodeViz
官網:
http://www.csn.ul.ie/~mel/projects/codeviz/
安裝使用:
CodeViz —— 一款分析C_C++源代碼中函數調用關係的調用圖生成工具.pdf
http://linux.chinaunix.net/bbs/thread-1031921-1-1.html
用CodeViz產生函數調用圖
http://barry-popy.blog.sohu.com/31629163.html
分析函數調用關係圖(call graph)的幾種方法
http://blog.csdn.net/Solstice/archive/2005/09/24/488865.aspx
用CodeViz繪製函數調用關係圖(call graph)
http://blog.csdn.net/Solstice/archive/2005/09/22/486788.aspx
2.ncc
對於源碼的閱讀工具,通常是選取後面提到的某種源碼索引工具,再和find以及grep「高低搭配」一塊兒來使用。
1.命令選項
2.正則表達式
Regular Expression HOWTO: http://www.amk.ca/Python/howto/regex/
wine + source insight
優勢: SI的特色是有圖形界面,操做和瀏覽特別方便快捷。特別是它的「函數調用樹」的圖形顯示功能,以及分窗口自動顯示函數,變量等定義的功能。
缺點: 不能解析彙編源文件。
在ubuntu/debian下用如下命令就能在線安裝wine
$ sudo apt-get install wine
安裝好後,就能看到wine的快捷菜單被添加到了任務欄的「應用程序」中。
wine安裝好後,就能夠像在windows同樣去安裝使用SI了。安裝完成後,SI的快捷菜單被添加到「應用程序」→「wine」→「programs」→「source insight3」中。之後用快捷菜單就能啓動SI
能夠亂點亂試一下,它能提供不少的功能。其中一些常常要到的功能有 查找符號;函數調用的函數,被調用的函數;以及調用關係的多層展開顯示;字符串搜索等。
[待玩] http://sourcenav.sourceforge.net/
安裝:
在ubuntu下能夠在線安裝
$ sudo apt-get install sourcenav
運行:
$ snavigator
參考:
cscope的官方教程 「The Vim/Cscope tutorial」:
http://cscope.sourceforge.net/cscope_vim_tutorial.html
對應的中文翻譯: http://www.gracecode.com/Archive/Display/316
http://www.lupaworld.com/?uid-151392-action-viewspace-itemid-106656
優勢: 本人感受在終端下看源碼比較舒服。
缺點: 沒有一個實時顯示函數/變量定義的分窗口。也不能直接顯示「調用樹」,但有其餘小工具能夠實現該功能。也許vim高手能解決這些問題。
ubuntu/debian下用如下命令就能在線安裝
$ sudo apt-get install cscope ctags
在終端下能夠用 man info –help等形式查看cscope/ctags的手冊
在vim下查看手冊的方式是
:help cscope 和 :help ctags
1. 如下是cscope創建索引文件用到的一些選項
-R: 在生成索引文件時,搜索子目錄樹中的代碼 -b: 只生成索引文件,不進入cscope的界面 -q: 生成cscope.in.out和cscope.po.out文件,加快cscope的索引速度 -k: 在生成索引文件時,不搜索/usr/include目錄 -i: 若是保存文件列表的文件名不是cscope.files時,須要加此選項告訴cscope到哪兒去找源文件列表。能夠使用「-」,表示由標準輸入得到文件列表。 -I dir: 在-I選項指出的目錄中查找頭文件 -u: 掃描全部文件,從新生成交叉索引文件 -C: 在搜索時忽略大小寫 -P path: 在以相對路徑表示的文件前加上的path,這樣,你不用切換到你數據庫文件所在的目錄也能夠使用它了。
2. 在vim下利用:cscope find <關鍵字> 命令的選項有
s: 查找C語言符號,即查找函數名、宏、枚舉值等出現的地方 g: 查找函數、宏、枚舉等定義的位置,相似ctags所提供的功能 d: 查找本函數調用的函數 c: 查找調用本函數的函數 t: 查找指定的字符串 e: 查找egrep模式,至關於egrep功能,但查找速度快多了 f: 查找並打開文件,相似vim的find功能 i: 查找包含本文件的文
[可能要修改]
用如下命令先產生一個文件列表,而後讓cscope爲這個列表中的每一個文件都生成索引。在這裏,咱們只關注.h, .c, .S文件,因此只對他們進行索引。能夠根據本身需求進行更改。接着咱們用-bq選項利用cscope生成索引。選項意義見上節。同時也生成ctags索引。
#!/bin/sh find . -name "*.h" -o -name "*.c" -o -name "*.S" > cscope.files cscope -bkq -i cscope.files ctags -R
切換到內核源碼的目錄上,運行vim,而後在vim下導入索引
$vim :cscope add cscope.out
而後就能夠在vim下調用「:cscope find <關鍵字>」來查找函數的定義,函數調用的函數以及被調用函數等
「:cscope find <關鍵字>」 能夠縮寫爲 「:cs f <關鍵字>」
好比如下命令用來查找sys_read的定義
:cs f g sys_read
「cs f」的其餘命令選項請看上節
ctrl + t : 退回 ctrl + ] : 進入光標處的變量/函數的定義處
kscope是cscope的圖形前端工具。在ubuntu下能夠在線安裝。它的界面上和操做上與source insight都比較相似。可是目前它對cpu的佔用很大,不是很好。可是它和cscope相比,有一個很大的優勢是:能夠圖形顯示「函數調用樹」,甚至這個功能比SI還強大。
$sudo apt-get install kscope
1. 優缺點
優勢:自己好像沒什麼特別的優勢。可是有專門提供這種服務的網站,上面有不少不一樣系統的不一樣版本源碼
缺點:在本機上配置運行的話,配置麻煩。若是是瀏覽lxr站點的方式,速度比較慢。
2. lxr官方: http://lxr.linux.no/
特色是能夠瀏覽歷史上linux全部版本的源碼,能夠看到它的演化過程。
3. 其餘系統的源碼 http://fxr.watson.org/
估計超一流的內核開發人員,可能會常常訪問此類站點。由於他須要借鑑其餘系統的設計思想。
在源碼閱讀的功能上:
1. SI等適合「面讀」,也就是讀一個代碼段,而且提供更溫馨的閱讀輔助手段。SI適合分析函數全面的邏輯。
2. gdb適合「線讀」,也就是以追蹤調用鏈的方式深刻閱讀,而且提供了數據分析的調試功能。適合分析特定狀況下的函數邏輯表現。
爲了能使用調試器,必須理解函數調用鏈在調試器級別的表現形式。可是,由於存在內嵌函數和代碼優化等緣由,調試器的表現形式和源碼瀏覽器下的表現形式是不同的。它們二者的信息顯示可能存在「錯位」的現象。本節的目的就是爲了磨合調試器和交叉索引工具之間的代溝。
爲了簡化問題的描述,在實際分析前,先將知識點分解介紹一下。
下面我給出一個處於「理想狀態」的經典backtrace(backtrace的意思是「回溯」,依照它的做用來講,也就是本人說的調用鏈)。所謂「理想狀態的」的backtrace是指,能夠利用內核源碼交叉索引工具,依據gdb給出的這個backtrace,從frame 0開始一級級日後最追溯,可以一直追溯到最前面的frame N,並且追溯的過程當中,沒有出現多出來的鏈接frameN和frame(N-1)的「過渡」frame.
注意其中的兩個條件:1.可以 2.很少出。可是,在現實的世界裏,每每沒這麼美好。源碼瀏覽工具每每要麼「不能」,要麼「多出」。形成前者的緣由在於源碼瀏覽工具的侷限性,形成後者的是內嵌函數以及代碼優化。詳細狀況可看下節的分析。
追溯的方法對於source insight來講就是:打開」relation window」→選中要被追溯的函數→右鍵→選「view relation」→選「referenced by functions」,這樣就能顯示出調用了被選函數的函數來。
咱們拿下面這個「理想狀態」的backtrace分析一下
(gdb) bt #0 kref_init (kref=0xdc40abe4) at lib/kref.c:33 #1 0xc01de8be in kobject_init_internal (kobj=0xdc40abe0) at lib/kobject.c:149 #2 0xc01de928 in kobject_init (kobj=0xdc40abe0, ktype=0xc035b9dc) at lib/kobject.c:282 #3 0xc01de972 in kobject_create () at lib/kobject.c:619 #4 0xc01def53 in kobject_create_and_add (name=0xdc40abe4 "", parent=0xc035b9dc) at lib/kobject.c:641 #5 0xc0393b04 in mnt_init () at fs/namespace.c:2333 #6 0xc039382b in vfs_caches_init (mempages=108676) at fs/dcache.c:2212 #7 0xc037f868 in start_kernel () at init/main.c:666 #8 0xc037f008 in i386_start_kernel () at arch/x86/kernel/head32.c:13 #9 0x00000000 in ?? ()
理想狀態下的backtrace各個域的含義是(注意,在非理想狀態的backtrace中,這些含義每每對不上號)
#frameN的編號 frame(N-1)的返回地址(注:fram0沒有這項) in frameN所處的函數(該函數的參數...) at 該函數所處的源文件 : frameN函數內對frame(N-1)函數的調用語句在源文件中所處的行數
咱們看下
#0 kref_init (kref=0xdc40abe4) at lib/kref.c:33
它說明frame0時,kref_init正要運行。傳入的參數是0xdc40abe4。函數kref_init從源文件lib/kref.c第33行開始。 在gdb下調用shell來查看源文件
(gdb) shell vi lib/kref.c
vi 出來後打命令:set nu可看到
31 */ 32 void kref_init(struct kref *kref) 33 { 34 kref_set(kref, 1); 35 } 36
咱們再看看frame0這一瞬間是否是「kref_init正要運行」。應該知道,「正要運行」和「正要被調用」是兩個不一樣的概念。前者來講,到了下一個指令,代碼的控制權就會交給了被調用的函數;然後者,到了下一個指令,代碼的控制權還在調用者手裏,
(gdb) f 0 #0 kref_init (kref=0xdc40abe4) at lib/kref.c:33 33 { (gdb) info registers .... edi 0x0 0 eip 0xc01df520 0xc01df520 <kref_init> //<-注意eip是下一個將要運行的指令地址 eflags 0x282 [ SF IF ] .... (gdb) disass kref_init Dump of assembler code for function kref_init: 0xc01df520 <kref_init+0>: push %ebp //對比上面,eip指向這裏 0xc01df521 <kref_init+1>: mov %esp,%ebp ... 0xc01df52f <kref_init+15>: ret End of assembler dump. (gdb)
可見,kobject_init_internal的調用指令call已經執行完畢,到了frame0時,下一個指令「將要運行」函數kref_init。
再看看
#1 0xc01de8be in kobject_init_internal (kobj=0xdc40abe0) at lib/kobject.c:149
frameN與frame(N-1)之間是調用的關係,前者調用了後者。也就是說,frame1的kobject_init_internal調用frame0的kref_init,而且kref_init函數返回後,將返回到地址0xc01de8be繼續執行。0xc01de8be就在kobject_init_internal的體內,函數kobject_init_internal中調用kref_init的C語句位於lib/kobject.c的149行。
查看一下kobject_init_internal的反彙編碼
(gdb) disass kobject_init_internal Dump of assembler code for function kobject_init_internal: 0xc01de8ac <kobject_init_internal+0>: push %ebp 0xc01de8ad <kobject_init_internal+1>: test %eax,%eax 0xc01de8af <kobject_init_internal+3>: mov %esp,%ebp 0xc01de8b1 <kobject_init_internal+5>: push %ebx 0xc01de8b2 <kobject_init_internal+6>: mov %eax,%ebx 0xc01de8b4 <kobject_init_internal+8>: je 0xc01de8d3 <kobject_init_internal+39> 0xc01de8b6 <kobject_init_internal+10>: lea 0x4(%eax),%eax 0xc01de8b9 <kobject_init_internal+13>: call 0xc01df520 <kref_init> 0xc01de8be <kobject_init_internal+18>: lea 0x8(%ebx),%eax //注意這個地址0xc01de8be是kref_init的返回地址 0xc01de8c1 <kobject_init_internal+21>: mov %eax,0x8(%ebx)
再看看lib/kobject.c,看看最後的那個行數的意義
145 static void kobject_init_internal(struct kobject *kobj) 146 { 147 if (!kobj) 148 return; 149 kref_init(&kobj->kref); //注意kobject_init_internal調用子函數kref_init的C語句位於行數149 150 INIT_LIST_HEAD(&kobj->entry); 151 kobj->state_in_sysfs = 0; 152 kobj->state_add_uevent_sent = 0; 153 kobj->state_remove_uevent_sent = 0; 154 kobj->state_initialized = 1; 155 }
在驗證一下
#2 0xc01de928 in kobject_init (kobj=0xdc40abe0, ktype=0xc035b9dc) at lib/kobject.c:282
看看kobject_init的反彙編碼
(gdb) disass kobject_init Dump of assembler code for function kobject_init: 0xc01de8f3 <kobject_init+0>: push %ebp ........ 0xc01de923 <kobject_init+48>: call 0xc01de8ac <kobject_init_internal> 0xc01de928 <kobject_init+53>: mov %esi,0x18(%ebx) //注意這個地址0xc01de928是kobject_init_internal的返回地址 ...... 0xc01de94b <kobject_init+88>: pop %ebp 0xc01de94c <kobject_init+89>: ret End of assembler dump.
看看看看lib/kobject.c,看看最後的那個行數的意義
263 void kobject_init(struct kobject *kobj, struct kobj_type *ktype) 264 { 265 char *err_str; ....... 282 kobject_init_internal(kobj); ////注意kobject_init調用子函數kobject_init_internal的C語句位於行數282 283 kobj->ktype = ktype; ...... 287 printk(KERN_ERR "kobject (%p): %s/n", kobj, err_str); 288 dump_stack
經過這兩個例子,可見最初的猜測是正確的。
本小節意義: 在利用SI等工具查看函數調用鏈時,遇到的一個最多的問題是函數指針的調用。因此把該小節內容移到這裏來,爲下小節的敘述做鋪墊。SI等交叉索引工具不能在父函數內部解析出這種調用關係。
咱們常常碰到這種狀況:若是內核中函數A是經過函數指針調用函數B,那麼源碼交叉索引工具(如source insight, kscope等)就沒法經過函數B的名稱回溯到上層函數A。這是由於在函數A內部對函數B的調用並非經過函數B的名稱,而是利用指向函數B代碼塊的指針(函數指針)。
要想解決這個問題,方法有兩種:
1. 利用字符串搜索功能:
搜索函數指針的變量名。若是已經知道的是子函數,想找出經過指針調用它的全部上層父函數:利用子函數的函數名進行搜索,就能找到全部相應的函數指針變量賦值的語句。而後搜索該函數指針變量就能獲得全部可能調用該函數的上層父函數。相反,若是是已經知道父函數,想知道該父函數體內的一個函數指針可能會調用哪些子函數,能夠搜索該函數指針變量(通常在該變量名前加個點號「.」),這樣能夠搜索出全部給該函數指針變量賦值的語句,從而找出全部可能的子函數。
固然,既然是字符串搜索,搜索結果中會夾帶其餘沒用的信息,這須要進一步的篩選。這個方法能搜索出依賴某函數指針變量的全部調用關係。
2. 利用調試工具:
在目標函數處下斷點。調試器器會實時攔截該函數的調用,而後用bt命令就能看到整個調用鏈。
這個方法獲得的只是一個特定的具體調用關係。可能還有其餘不少的潛在調用路徑。
然而,咱們研究的目標並不知足於知道調用鏈。下面咱們觀察函數到底是怎樣利用函數指針調用子函數的。[待整理]
2130 int vfs_mkdir(struct inode *dir, struct dentry *dentry, int mode) 2131 { 2132 int error = may_create(dir, dentry, NULL); 2133 2134 if (error) 2135 return error; 2136 2137 if (!dir->i_op || !dir->i_op->mkdir) 2138 return -EPERM; 2139 2140 mode &= (S_IRWXUGO|S_ISVTX); 2141 error = security_inode_mkdir(dir, dentry, mode); 2142 if (error) 2143 return error; 2144 2145 DQUOT_INIT(dir); 2146 error = dir->i_op->mkdir(dir, dentry, mode); 2147 if (!error) 2148 fsnotify_mkdir(dir, dentry); 2149 return error; 2150 } 對源碼文件下斷點 (gdb) b fs/namei.c:2146 Breakpoint 9 at 0xc017c0ee: file fs/namei.c, line 2146. 問題一: 動態分析call *0x14(%ebx)是怎麼回事,函數指針 ------------------------- ┌──Register group: general───────────────────────────────────────────────────────────────────────────────────────────────────┐ │eax 0xdc20b0a8 -601837400 ecx 0x1ed 493 │ │edx 0xdb9526c0 -610982208 ebx 0xe01c87d4 -535001132 │ │esp 0xd8c5bf1c 0xd8c5bf1c ebp 0xd8c5bf34 0xd8c5bf34 │ │esi 0xdc20b0a8 -601837400 edi 0xdb9526c0 -610982208 │ │eip 0xc017c0fb 0xc017c0fb <vfs_mkdir+179> eflags 0x200246 [ PF ZF IF ID ] │ │cs 0x60 96 ss 0x68 104 │ │ds 0x7b 123 es 0x7b 123 │ │fs 0xd8 216 gs 0x33 51 │ │ │ │ │ │ │ │ │ │ │ │ │ ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │0xc017c0ea <vfs_mkdir+162> mov %esi,%eax │ │0xc017c0ec <vfs_mkdir+164> call *(%ecx) │ B+ │0xc017c0ee <vfs_mkdir+166> mov 0x98(%esi),%ebx │ │0xc017c0f4 <vfs_mkdir+172> mov %edi,%edx │ │0xc017c0f6 <vfs_mkdir+174> mov %esi,%eax │ │0xc017c0f8 <vfs_mkdir+176> mov -0x10(%ebp),%ecx │ >│0xc017c0fb <vfs_mkdir+179> call *0x14(%ebx) │ │0xc017c0fe <vfs_mkdir+182> test %eax,%eax │ │0xc017c100 <vfs_mkdir+184> mov %eax,%ebx │ │0xc017c102 <vfs_mkdir+186> jne 0xc017c15d <vfs_mkdir+277> │ │0xc017c104 <vfs_mkdir+188> testb $0x4,0x11c(%esi) │ │0xc017c10b <vfs_mkdir+195> je 0xc017c119 <vfs_mkdir+209> │ │0xc017c10d <vfs_mkdir+197> mov $0x4,%edx │ │0xc017c112 <vfs_mkdir+202> mov %esi,%eax │ └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ remote Thread 42000 In: vfs_mkdir Line: 2146 PC: 0xc017c0fb i_state = 1, dirtied_when = 0, i_flags = 0, i_writecount = { counter = 0 }, i_security = 0x0, i_private = 0x0 } (gdb) p/x $ebx $20 = 0xe01c87d4 (gdb) p/x $ebx+0x14 $21 = 0xe01c87e8 (gdb) p &sfs_dir_inode_ops $13 = (struct inode_operations *) 0xe01c87d4 (gdb) p/x *(int * )0xe01c87d4@10 $18 = {0xe01c75b1, 0xe01c7677, 0xc018d3f0, 0xc018cc91, 0xe01c75dd, 0xe01c75c0, 0xc018d441, 0xe01c7510, 0xc018d474, 0x0} (gdb) disass sfs_mkdir Dump of assembler code for function sfs_mkdir: 0xe01c75c0 <sfs_mkdir+0>: push %ebp //<- 0xe01c75c1 <sfs_mkdir+1>: or $0x40,%ch 0xe01c75c4 <sfs_mkdir+4>: mov %esp,%ebp 0xe01c75c6 <sfs_mkdir+6>: push %ebx 0xe01c75c7 <sfs_mkdir+7>: mov %eax,%ebx 0xe01c75c9 <sfs_mkdir+9>: push $0x0 0xe01c75cb <sfs_mkdir+11>: call 0xe01c7510 <sfs_mknod> 0xe01c75d0 <sfs_mkdir+16>: pop %edx 0xe01c75d1 <sfs_mkdir+17>: test %eax,%eax 0xe01c75d3 <sfs_mkdir+19>: jne 0xe01c75d8 <sfs_mkdir+24> 0xe01c75d5 <sfs_mkdir+21>: incl 0x28(%ebx) 0xe01c75d8 <sfs_mkdir+24>: mov -0x4(%ebp),%ebx 0xe01c75db <sfs_mkdir+27>: leave 0xe01c75dc <sfs_mkdir+28>: ret End of assembler dump. (gdb) p/x *0xe01c87e8 $9 = 0xe01c75c0 // <-sfs_mkdir的地址 (gdb) struct inode_operations { int (*create) (struct inode *,struct dentry *,int, struct nameidata *); struct dentry * (*lookup) (struct inode *,struct dentry *, struct nameidata *); int (*link) (struct dentry *,struct inode *,struct dentry *); int (*unlink) (struct inode *,struct dentry *); int (*symlink) (struct inode *,struct dentry *,const char *); int (*mkdir) (struct inode *,struct dentry *,int); ...... }; struct inode_operations sfs_dir_inode_ops = { ... .mkdir = sfs_mkdir, ... }; ----------------------------------------------------------------------- 0xc017c0fb <vfs_mkdir+179> call *0x14(%ebx) 爲何要加 * ? call *0x14(%ebx) == push %eip mov 0x14(%ebx) %eip 注意call與mov指令語義的區別 mov 0x14(%ebx) %eax; 把存放在地址0x14(%ebx)中的32位數據拷貝到%eax mov %eax 0x14(%ebx); 把%eax的值拷貝到地址0x14(%ebx)指向的內存中 call 0x14(%ebx) : 結果是跳到地址0x14(%ebx)繼續執行(固然對於本例來講,該地址指向的並非目標代碼段) call *0x14(%ebx) : 取出存放在地址0x14(%ebx)中的32位數據,把該數據做爲目標地址,跳到該地址繼續執行。 mov $0xe01c75c9 %eax ; 0xe01c75c9被認爲是當即數,前面有$。沒有mov 0xe01c75c9 %eax這種形式 call 0xe01c75c9 ;0xe01c75c9被認爲是地址。沒有call $0xe01c75c9這種形式。 注意,也沒有call %eax等形式(假設%eax放着目標地址)。需用 call *%eax,一樣,*%eax表示從%eax獲取地址值 | - | | -- | | - | | -- | 4. call sfs_mkdir == call 0xe01c75c0 | - | | -- | +--------------------+ <-------> | -- | 3. 0xe01c75c0 == fetch from 0xe01c87e8 | --- | | -- | *0x14(%ebx) +--------------------+ +---------------+ | init (*mkdir)(..) +--+ | 0xe01c75c0 | 2. 0xe01c87e8 == calculate 0x14(%ebx) 0x14(%ebx)---> +--------------------+ | +---------------+ | ... | | | 0xe01c75dd | +--------------------+ | +---------------+ | ... | | | 0xc018cc91 | +--------------------+ | +---------------+ | ... | | | 0xc018d3f0 | +--------------------+ | +---------------+ 1. 0xe01c87d4 == fetch from %ebx | ... | | | 0xe01c7677 | +--------------------+ | +---------------+ +------------+ | int (*create)(..) | | | 0xe01c75b1 | 0xe01c87d4 | 0xe01c87d4 | %ebx-------> +--------------------+ | +---------------+ +------------+ struct inode_operations | contents address register %ebx sfs_dir_inode_ops | | call *0x14(%ebx) 的過程 | +-----------------------------+ | static int sfs_mkdir(..) | 0xe01c75c0 <sfs_mkdir+0>: +-> push %ebp 0xe01c75c1 <sfs_mkdir+1>: or $0x40,%ch 0xe01c75c4 <sfs_mkdir+4>: mov %esp,%ebp 0xe01c75c6 <sfs_mkdir+6>: push %ebx 0xe01c75c7 <sfs_mkdir+7>: mov %eax,%ebx 0xe01c75c9 <sfs_mkdir+9>: push $0x0 0xe01c75cb <sfs_mkdir+11>: call 0xe01c7510 <sfs_mknod> 0xe01c75d0 <sfs_mkdir+16>: pop %edx 0xe01c75d1 <sfs_mkdir+17>: test %eax,%eax 0xe01c75d3 <sfs_mkdir+19>: jne 0xe01c75d8 <sfs_mkdir+24> 0xe01c75d5 <sfs_mkdir+21>: incl 0x28(%ebx) 0xe01c75d8 <sfs_mkdir+24>: mov -0x4(%ebp),%ebx 0xe01c75db <sfs_mkdir+27>: leave 0xe01c75dc <sfs_mkdir+28>: ret address contents --------------------------------------------------------------------------- 問題二: 下面的dir->i_op->mkdir(),爲何不是dir.i_op.mkidr. . 和 -> 有什麼區別 static int sfs_mkdir(struct inode * dir, struct dentry * dentry, int mode) { .... } 2130 int vfs_mkdir(struct inode *dir, struct dentry *dentry, int mode) 2131 { .... 2146 error = dir->i_op->mkdir(dir, dentry, mode); ... 2150 } struct inode { ... const struct inode_operations *i_op; ... }; struct inode_operations { ... int (*mkdir) (struct inode *,struct dentry *,int); ... }; dir: 取得(struct inode *)dir dir->i_op: 取得(const struct inode_operations *)i_op dir->i_op->mkdir: 取得(int (*) (struct inode *,struct dentry *,int))mkdir dir->i_op->mkdir(dir, dentry, mode)也就是 函數指針變量名(參數...) 函數指針是一個指針,它向目標函數的代碼塊的第一個指令。 函數名的值等於該函數第一條指令的地址。 (gdb) p sfs_mkdir $20 = {int (struct inode *, struct dentry *, int)} 0xe01c75c0 <sfs_mkdir> (gdb) p &sfs_mkdir $21 = (int (*)(struct inode *, struct dentry *, int)) 0xe01c75c0 <sfs_mkdir> (gdb) p dir->i_op->mkdir $18 = (int (*)(struct inode *, struct dentry *, int)) 0xe01c75c0 <sfs_mkdir> 前者指明變量名/函數名的類型,後者是它的值 struct inode_operations sfs_dir_inode_ops = { ... .mkdir = sfs_mkdir, ... }; 函數的兩種調用形式: 函數指針變量名(參數...) 函數名(參數...) 嚴格地說,從C語言的形式看來,前者經過函數指針變量名調用函數,後者經過函數名調用,是不一樣的。 但從彙編級代碼看來,都是轉化爲指令call 函數地址。是同樣的。 引入了函數指針變量後,這個變量就能夠動態地賦值,從而指向不一樣的函數體,實現某些特殊的功能。 咱們再看下函數指針的賦值.mkdir = sfs_mkdir, 嚴格地說,mkdir和sfs_mkdir是類型不一樣的東西,但在編譯時自動通過了類型轉換。因此下面這些寫法效果都同樣 .mkdir = sfs_mkdir, .mkdir = &sfs_mkdir, .mkdir = (int (*)(struct inode *, struct dentry *, int))sfs_mkdir, .mkdir = (int (*)(struct inode *, struct dentry *, int))(&sfs_mkdir), --- 例子 #include <stdio.h> int main() { int myfunc(int a) { printf("%d/n", a); return 0; } int (*funa)(int) = myfunc; int (*funb)(int) = &myfunc; int (*func)(int) = (int (*)(int))myfunc; int (*fund)(int) = (int (*)(int))(&myfunc); myfunc(1); funa(2); funb(3); func(4); fund(5); return 0; }
1. 人觀念層次
2. 交叉解析器層次
2. c調用層次
3. 編譯器(機器碼靜態)層次
4. 運行時(機器碼動態)層次,也叫調試器層次
很明顯,前面所講的「理想狀態」的backtrace就是指在交叉解析器層次下和在調試器層次下的表現相同的調用鏈。
任務:
從一個斷點開始,從後向前推導,分析出ramfs註冊函數的調用過程。同時,觀察調試器的優勢和侷限性。
ramfs文件系統的註冊函數是register_filesystem(&ramfs_fs_type)。爲了更快定位,在上層函數init_ramfs_fs下斷點。然後在gdb下獲得的調用鏈是
(gdb) bt #0 register_filesystem (fs=0xc03595cc) at fs/filesystems.c:68 #1 0xc0394594 in init_ramfs_fs () at fs/ramfs/inode.c:213 #2 0xc037f473 in kernel_init (unused=<value optimized out>) at init/main.c:708 #3 0xc010463f in kernel_thread_helper () at arch/x86/kernel/entry_32.S:1013
咱們注意到:
1. 這個backtrace包含的函數只有4個,實際上並不是如此。通過分析,它實際上(用C的觀點看)調用鏈以下所示,這是爲何呢?
start_kernel→rest_init→kernel_thread→kernel_thread_helper→call %ebx (即call kernel_init)→do_basic_setup→do_initcalls→do_one_initcall→result = fn() (即call init_ramfs_fs)→register_filesystem
2. backtrace推溯到kernel_thread_helper後就再沒下文了。又是什麼使得調試器變成了瞎子,沒法看得再遠了呢?
欲見其詳,且聽下回分解
[下面準備材料]
kernel_init對do_basic_setup的調用被優化成內聯函數
do_basic_setup對do_initcalls的調用被優化成內聯函數
do_initcalls對do_one_initcall的調用被優化成內聯函數
有三層的非內聯函數都被被優化成內聯函數,整個代碼被優化的亂七八糟。
838 static int __init kernel_init(void * unused) 839 { ..... 864 cpuset_init_smp(); 865 866 do_basic_setup(); 867 ....... 887 return 0; 888 } static void __init do_basic_setup(void) { /* drivers will send hotplug events */ init_workqueues(); usermodehelper_init(); driver_init(); init_irq_proc(); do_initcalls(); } 741 static void __init do_initcalls(void) 742 { 743 initcall_t *call; 744 745 for (call = __initcall_start; call < __initcall_end; call++) 746 do_one_initcall(*call); 747 748 /* Make sure there is no pending stuff from the initcall sequence */ 749 flush_scheduled_work(); 750 } static void __init do_one_initcall(initcall_t fn) { int count = preempt_count(); ktime_t t0, t1, delta; char msgbuf[64]; int result; if (initcall_debug) { print_fn_descriptor_symbol("calling %s/n", fn); t0 = ktime_get(); } result = fn(); if (initcall_debug) { .... } static inline void print_fn_descriptor_symbol(const char *fmt, void *addr) { #if defined(CONFIG_IA64) || defined(CONFIG_PPC64) addr = *(void **)addr; #endif print_symbol(fmt, (unsigned long)addr); } (gdb) disass kernel_init Dump of assembler code for function kernel_init: 0xc037f349 <kernel_init+0>: push %ebp 0xc037f34a <kernel_init+1>: mov %esp,%ebp 0xc037f34c <kernel_init+3>: push %edi 0xc037f34d <kernel_init+4>: push %esi ...... 0xc037f413 <kernel_init+202>: call 0xc0391454 <cpuset_init_smp> 0xc037f418 <kernel_init+207>: call 0xc0390081 <init_workqueues> //<-do_basic_setup被優化成內聯函數,在這裏開始展開 0xc037f41d <kernel_init+212>: call 0xc039004e <usermodehelper_init> 0xc037f422 <kernel_init+217>: call 0xc039b7d1 <driver_init> 0xc037f427 <kernel_init+222>: call 0xc0153e18 <init_irq_proc> 0xc037f42c <kernel_init+227>: movl $0xc03aa470,-0x5c(%ebp) //do_initcalls被優化成內聯函數,在這裏開始展開 0xc037f433 <kernel_init+234>: pop %eax 0xc037f434 <kernel_init+235>: pop %edx 0xc037f435 <kernel_init+236>: jmp 0xc037f559 <kernel_init+528> 0xc037f43a <kernel_init+241>: mov -0x5c(%ebp),%eax //do_one_initcall被優化成內聯函數,在這裏開始展開 0xc037f43d <kernel_init+244>: mov (%eax),%eax 0xc037f43f <kernel_init+246>: mov %eax,-0x58(%ebp) 0xc037f442 <kernel_init+249>: mov %esp,%eax 0xc037f444 <kernel_init+251>: and $0xffffe000,%eax 0xc037f449 <kernel_init+256>: mov 0x14(%eax),%eax 0xc037f44c <kernel_init+259>: cmpl $0x0,0xc03a1820 0xc037f453 <kernel_init+266>: mov %eax,-0x54(%ebp) 0xc037f456 <kernel_init+269>: je 0xc037f470 <kernel_init+295> 0xc037f458 <kernel_init+271>: mov -0x58(%ebp),%edx //內聯函數print_fn_descriptor_symbol在這裏開始展開 0xc037f45b <kernel_init+274>: mov $0xc030d1be,%eax 0xc037f460 <kernel_init+279>: call 0xc013f598 <__print_symbol>//內聯函數print_fn_descriptor_symbo的展開結束 0xc037f465 <kernel_init+284>: call 0xc013352f <ktime_get> 0xc037f46a <kernel_init+289>: mov %eax,-0x64(%ebp) 0xc037f46d <kernel_init+292>: mov %edx,-0x60(%ebp) 0xc037f470 <kernel_init+295>: call *-0x58(%ebp) //do_one_initcall中的調用語句result = fn(); ..... 0xc037f553 <kernel_init+522>: pop %edi 0xc037f554 <kernel_init+523>: pop %eax 0xc037f555 <kernel_init+524>: addl $0x4,-0x5c(%ebp) 0xc037f559 <kernel_init+528>: cmpl $0xc03aa804,-0x5c(%ebp) // 0xc037f560 <kernel_init+535>: jb 0xc037f43a <kernel_init+241> //
如何在彙編碼中定位內聯(或被優化掉的非內聯)函數
1.利用先後相關函數的提示 2.函數的前戲碼定位函數的開始 3.注意跳轉語句 4.利用調試器輔助定位(見gdb技巧)
[待充實]
一個複雜的函數調用必定是調用了多個子函數,同時這些子函數又會調用若干「孫」函數,這樣依次調用並依次返回到最初的父函數後,就造成了樹狀的調用關係,咱們稱之爲「調用樹」。
函數調用樹是比函數調用鏈更爲複雜的觀察對象。若是可以顯示調用樹,就能夠對調用的整個過程有個直觀的瞭解。
函數調用樹有兩類:
1. 抽象調用樹
也叫虛擬調用樹。好比在源碼中,父函數調用了子函數a, b, c。那麼對這三個函數的調用邏輯都考慮進去,這就是「抽象調用」。抽象調用樹能全面的描述了父函數的邏輯和代碼開發員的意圖。可是,在實際的環境中,這三個函數未必就所有會調用到。把在實際的具體狀況下未調用的「潛在」調用關係去掉後,剩下的調用樹就稱爲「具體調用樹」。明顯,具體調用樹不能全面顯示代碼開發員的意圖,只是放映具體環境下函數的調用關係。
2. 具體調用樹
也叫實時調用樹。解釋見上。
1. 抽象調用樹的顯示
藉助source insight等工具能夠圖形顯示抽象調用樹。
1. 具體調用樹的顯示
據本人的瞭解,目前gdb沒有一個相似」bt」那樣的能顯示函數調用樹的命令,可是藉助gdb宏也許可以實現顯示調用樹的功能,這有待研究。不過,目前已經有個現成的調試工具能夠顯示調用樹,它就是 systemtap.
效果以下:
[...] 0 klogd(1391):->sys_read 14 klogd(1391): ->fget_light 22 klogd(1391): <-fget_light 27 klogd(1391): ->vfs_read 35 klogd(1391): ->rw_verify_area 43 klogd(1391): <-rw_verify_area 49 klogd(1391): ->kmsg_read 0 sendmail(1696):->sys_read 17 sendmail(1696): ->fget_light 26 sendmail(1696): <-fget_light 34 sendmail(1696): ->vfs_read 44 sendmail(1696): ->rw_verify_area 52 sendmail(1696): <-rw_verify_area 58 sendmail(1696): ->proc_file_read 70 sendmail(1696): ->loadavg_read_proc 84 sendmail(1696): ->proc_calc_metrics 92 sendmail(1696): <-proc_calc_metrics 95 sendmail(1696): <-loadavg_read_proc 101 sendmail(1696): <-proc_file_read 106 sendmail(1696): ->dnotify_parent 115 sendmail(1696): <-dnotify_parent 119 sendmail(1696): ->inotify_dentry_parent_queue_event 127 sendmail(1696): <-inotify_dentry_parent_queue_event 133 sendmail(1696): ->inotify_inode_queue_event 141 sendmail(1696): <-inotify_inode_queue_event 146 sendmail(1696): <-vfs_read 151 sendmail(1696):<-sys_read [...]
見於
http://sourceware.org/systemtap/wiki/WSCallGraph?highlight=1)
對於一個更刁的函數調用來講,利用工具顯示的抽象調用樹和具體調用調用樹多是不完整的。好比,對於抽象調用樹來講,它的顯示工具是source insight。可是若是這個函數對某個子函數或在更下層的函數對下下層的函數調用是經過函數指針來調用的,那麼source insight顯示的調用樹中就會漏掉經過函數指針調用的子函數,以及以子函數爲根的子調用樹。這是由於函數指針變量的賦值是發生在代碼動態運行時的。source insight沒法利用靜態的源碼就捕捉到將來纔出現東西,甚至它也沒法在形式上解析出「那裏存在一個利用函數指針的調用」。這就要經過閱讀源碼來找出這種調用關係。同時,能夠利用調試器實時找出具體狀況下是經過那個函數指針調用了哪一個特定的下層函數。這樣就能把漏掉的子調用樹拼接到父調用樹中。
可見,這些內容又迴歸到了調用鏈的內容。具體看前面。
各函數間的像蜘蛛網同樣的調用關係的圖形表示就是調用圖了,顯然它比調用樹更復雜。
本節意義:通過上面章節的敘述,利用源碼交叉索引工具+調試器已經能解決大部分問題,可是由於調試器和交叉索引工具的各自侷限性,依然會存在一些問題。本節嘗試如何聯合交叉索引工具以及調試器再加上人腦來解決各自的缺點。
[觀察積累中,待擴展]
該小節內容移到了: 調用鏈的狀態→函數指針調用
咱們知道,一個函數的計算結果並不都是經過它的返回值返回的,有時會經過函數的參數返回真正感興趣的數據。看內核源碼的時候,若是調用鏈過長,涉及內容和數據結構過多的話,每每是看到最後都記不住函數的參數哪些是已經「初始化的」。
這也是交叉索引工具沒法克服的先天弱點。它能動態索引源碼,卻沒法動態查看數據。此時,能夠利用gdb給目標函數下斷點,然後能夠用命令info args查看參數,另外命令info local可查看本地變量。固然在ddd下查看效果會更好。
內容簡單,不展開了。
實例 「什麼/proc下沒法建立目錄?」
若是對目標函數下斷點後,受到不少騷擾,那麼就轉爲在上層函數內對目標函數的調用指令處下斷點。若是你已經進入了上層函數,對調用指令下斷點,是更爲精確的斷點方法。
有時咱們調試的程序與中斷無關的,可是因爲時鐘中斷的異步到來,在調試過程當中常常會自動進入時鐘中斷處理例程中,這嚴重干擾了咱們的工做。用下面的方法可繞過期鍾中斷的干擾。
注:
使用GDB與QEMU調試內核時的問題分析: http://www.chinaitlab.com/linux/kernel/356774.html
關於qemu在單步指令時進入時鐘中斷的問題,上面給出連接給出了比較「深刻」地探討。這個問題涉及虛擬機自己,有人說是虛擬機相對於真機的固有缺陷,彷佛很深奧,我沒那個能力也沒那個時間研究。可是咱們應該知道,若是問題足夠的複雜,以致於解決它要花費過高的代價,那麼繞過這個問題是個更明智的解決方法。
解決方法(手工)
1. 內核啓動早期
事先下兩個斷點
b common_interrupt b native_iret
自定義返回命令
(gdb) define ooi Type commands for definition of "ooi". End with a line saying just "end". >c >stepi >end
一旦時鐘中斷產生,就會攔截在中斷處理的通用入口common_interrupt,而後運行返回指令,就會「回到」被時鐘中斷打斷的原指令處
ooi
2. 內核啓動完畢
事先下兩個斷點
b apic_timer_interrupt b irq_return
一旦時鐘中斷產生,就會攔截在中斷處理例程apic_timer_interrupt,而後運行返回指令,就會「回到」被時鐘中斷打斷的原指令處
ooi
分析記錄,待整理 提示,分析異常和中斷的處理過程比分析C代碼更直觀,由於源碼自己是彙編碼。 ┌──arch/x86/kernel/entry_32.S─────────────────────────────────────────────────────────────────────────────────────────────┐ │614 SAVE_ALL │ │615 TRACE_IRQS_OFF │ │616 movl %esp,%eax │ │617 call do_IRQ │ >│618 jmp ret_from_intr │ │619 ENDPROC(common_interrupt) │ │620 CFI_ENDPROC │ │621 │ │622 #define BUILD_INTERRUPT(name, nr) / │ │623 ENTRY(name) / │ │624 RING0_INT_FRAME; / │ │625 pushl $~(nr); / │ │626 CFI_ADJUST_CFA_OFFSET 4; / │ │627 SAVE_ALL; / │ └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ │0xc01043e1 <common_interrupt+17> mov %edx,%ds │ │0xc01043e3 <common_interrupt+19> mov %edx,%es │ │0xc01043e5 <common_interrupt+21> mov $0xd8,%edx │ │0xc01043ea <common_interrupt+26> mov %edx,%fs │ │0xc01043ec <common_interrupt+28> mov %esp,%eax │ │0xc01043ee <common_interrupt+30> call 0xc0106151 <do_IRQ> │ >│0xc01043f3 <common_interrupt+35> jmp 0xc01038dc <ret_from_exception> │ │0xc01043f8 <reschedule_interrupt> push $0xffffff03 │ │0xc01043fd <reschedule_interrupt+5> cld │ │0xc01043fe <reschedule_interrupt+6> push %fs │ │0xc0104400 <reschedule_interrupt+8> push %es │ │0xc0104401 <reschedule_interrupt+9> push %ds │ │0xc0104402 <reschedule_interrupt+10> push %eax │ │0xc0104403 <reschedule_interrupt+11> push %ebp │ └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ remote Thread 42000 In: common_interrupt Line: 618 PC: 0xc01043f3 (gdb) (gdb) (gdb) bt #0 common_interrupt () at arch/x86/kernel/entry_32.S:618 #1 0x00000292 in ?? () #2 0xc01880db in alloc_vfsmnt (name=0xc031dcf3 "rootfs") at include/linux/slab.h:266 #3 0xc0176919 in vfs_kern_mount (type=0xc0359678, flags=0, name=0xc031dcf3 "rootfs", data=0x0) at fs/super.c:896 #4 0xc0176a2f in do_kern_mount (fstype=0xc031dcf3 "rootfs", flags=0, name=0xc031dcf3 "rootfs", data=0x0) at fs/super.c:968 #5 0xc0393b33 in mnt_init () at fs/namespace.c:2285 #6 0xc039382b in vfs_caches_init (mempages=108676) at fs/dcache.c:2212 #7 0xc037f868 in start_kernel () at init/main.c:666 #8 0xc037f008 in i386_start_kernel () at arch/x86/kernel/head32.c:13 #9 0x00000000 in ?? () (gdb) disass (gdb) ---- ┌──arch/x86/kernel/entry_32.S─────────────────────────────────────────────────────────────────────────────────────────────┐ │401 cmpl $((SEGMENT_LDT << 8) | USER_RPL), %eax │ │402 CFI_REMEMBER_STATE │ │403 je ldt_ss # returning to user-space with LDT SS │ │404 restore_nocheck: │ │405 TRACE_IRQS_IRET │ │406 restore_nocheck_notrace: │ │407 RESTORE_REGS │ │408 addl $4, %esp # skip orig_eax/error_code │ │409 CFI_ADJUST_CFA_OFFSET -4 │ │410 irq_return: │ >│411 INTERRUPT_RETURN │ │412 .section .fixup,"ax" │ │413 ENTRY(iret_exc) │ │414 pushl $0 # no error code │ ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │0xc0103a61 <restore_nocheck_notrace> pop %ebx │ │0xc0103a62 <restore_nocheck_notrace+1> pop %ecx │ │0xc0103a63 <restore_nocheck_notrace+2> pop %edx │ │0xc0103a64 <restore_nocheck_notrace+3> pop %esi │ │0xc0103a65 <restore_nocheck_notrace+4> pop %edi │ │0xc0103a66 <restore_nocheck_notrace+5> pop %ebp │ │0xc0103a67 <restore_nocheck_notrace+6> pop %eax │ │0xc0103a68 <restore_nocheck_notrace+7> pop %ds │ │0xc0103a69 <restore_nocheck_notrace+8> pop %es │ │0xc0103a6a <restore_nocheck_notrace+9> pop %fs │ │0xc0103a6c <restore_nocheck_notrace+11> add $0x4,%esp │ >│0xc0103a6f <irq_return> jmp *%cs:0xc0353b54 │ │0xc0103a76 <ldt_ss> lar 0x3c(%esp),%eax │ │0xc0103a7b <ldt_ss+5> jne 0xc0103a61 <restore_nocheck_notrace> │ └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ remote Thread 42000 In: irq_return Line: 411 PC: 0xc0103a6f (gdb) stepi 0xc0103a64 in restore_nocheck_notrace () at arch/x86/kernel/entry_32.S:407
[主要研究定位bug的技巧,找出是哪條指令引起了panic彷佛很容易。但要找出錯誤產生的源頭彷佛是門藝術了]
通過上面章節的敘述,本小節問題的解決已不成問題了。再也不展開敘述。能夠參考下面連接。
參考手冊
「Using kgdb and the kgdb Internals」 http://www.kernel.org/pub/linux/kernel/people/jwessel/kgdb/index.html
kgdb官網 http://kgdb.linsyssoft.com/
參考書籍(freeebsd)
「Debugging Kernel Problems」 http://www.google.cn/search?q=Debugging+Kernel+Problems&ie=utf-8&oe=utf-8&aq=t&rls=com.ubuntu:zh-CN:unofficial&client=firefox-a
「Chapter 10 Kernel Debugging」 http://www.freebsd.org/doc/en_US.ISO8859-1/books/developers-handbook/kerneldebug.html
參考書籍(linux)
Chapter 14. Kernel Debugging Techniques of 「Embedded Linux Primer: A Practical, Real-World Approach」
http://book.opensourceproject.org.cn/embedded/embeddedprime/
參考文章
「掌握 Linux 調試技術」 http://www.ibm.com/developerworks/cn/linux/sdk/l-debug/index.html
「定位Oops的具體代碼行」 http://blog.chinaunix.net/u/12592/showart_1092733.html
「跟蹤內核 oops」 http://wiki.zh-kernel.org/doc/oops-tracing.txt
「例解Linux Kernel Debug」 http://blog.chinaunix.net/u/2108/showart_164703.html
「kernel debug的一些小手段」 http://blog.chinaunix.net/u/12592/showart_499502.html
「Kernel Debugging Techniques」 http://www.linuxjournal.com/article/9252
[參考文章]有的已過期,並且深度不夠。
這部分的內容側重於內核原理分析,其中涉及gdb調試器的內容不是不少,但它起的做用很關鍵,主要用於觀察內核數據的生成及變化,在對源碼理解有困惑時用於驗證本身的猜測。另外,gdb一個很重要的功能是,攔截經過函數指針調用的函數,從而追溯整個調用鏈,交叉索引工具沒法作到這點。
另外,調試內核時,利用gdb的「list 函數名」命令看到的C代碼都是當前處理器當前配置下內核實際運行的函數版本:」disass 函數名」看到的都是處理器實際運行時的機器代碼,也就是說define語句和inline函數已經被編譯器處理了,並且編譯器也完成了優化。因此,gdb自己就是一種不可替代的源碼瀏覽工具,它能篩選掉出實際運行的函數版本,又能呈現出實際運行的機器碼。
gdb遠程串口協議
http://sourceware.org/gdb/current/onlinedocs/gdb_34.html#SEC706
http://www.huihoo.org/mirrors/pub/embed/document/debugger/ew_GDB_RSP.pdf
Jason Wessel的linux-2.6-kgdb.git
http://git.kernel.org/?p=linux/kernel/git/jwessel/linux-2.6-kgdb.git;a=summary
gdb調試模式
(gdb) set debug serial 1 (gdb) set debug remote 1
[分析一個簡單的驅動,觀察函數調用流程。重點觀察驅動與驅動模型,以及和系統內核的交互過程。好比,中斷的整個生命週期。]
參考:
「Debugging kernel modules」 http://lwn.net/Articles/90913/
「Linux 系統內核的調試」 http://www.ibm.com/developerworks/cn/linux/l-kdb/
「Linux 可加載內核模塊剖析」 http://www.ibm.com/developerworks/cn/linux/l-lkm/
「使用 KGDB 調試 Linux 內核」 http://blog.chinaunix.net/u/8057/showart_1087126.html
「使用 /proc 文件系統來訪問 Linux 內核的內容」 http://www.ibm.com/developerworks/cn/linux/l-proc.html
如何查找出當前系統所安裝模塊驅動對應的源碼,從而對其作些修改等實驗?
提示:
1. lsmod 列出模塊名
2. modinfo 模塊名, 查看模塊信息
3. 模塊名,模塊信息中的別名,模塊的參數說明文字均可結合source insight查找該模塊的源碼文件;模塊信息中的模塊路徑也可用來定位對應源碼的路徑以及相關的kconfig文件,從而獲取更多相關信息。通常源碼文件的名稱就是模塊名或在模塊名的基礎上加上某些後綴,用模塊名的方法查找不出時再利用其餘信息查找。
4. 若是利用以上方法還找不到源文件,或者一個模塊對應着幾個源文件,可以使用最後的必殺絕招。好比lsmod後獲得一個sr_mod。咱們用modinfo sr_mod的獲得它的已編譯文件的路徑是 /lib/modules/2.6.24-19-generic/kernel/drivers/scsi/sr_mod.ko ;把它拷貝出來,並用命令objdump -d sr_mod.ko 查看它的機器碼,就能夠知道它使用了哪些函數,利用這些函數名就能夠結合source insight搜索出源碼了。
首先,在虛擬系統上裝入目標模塊foo,而後到/sys/module/foo/sections/下查看目標模塊的section偏移地址信息.
實例
debian:/sys/module/smplefs/sections# cat .text .data .bss 0xe01c7000 0xe01c864c 0xe01c8b20
而後,到真機的gdb下用add-symbol-file命令裝載目標模塊的符號信息 格式以下
add-symbol-file /path/to/module 0xe01c7000 / # .text -s .data 0xe01c864c / -s .bss 0xe01c8b20
實例
(gdb) add-symbol-file test/day11/samplefs.ko 0xe01c7000 -s .data 0xe01c864c -s .bss 0xe01c8b20 add symbol table from file "test/day11/samplefs.ko" at .text_addr = 0xe01c7000 .data_addr = 0xe01c864c .bss_addr = 0xe01c8b20 (y or n) y Reading symbols from /storage/myqemu/new/linux-2.6.26/test/day11/samplefs.ko...done. (gdb)
而後,餘下的對模塊的調試就相似對內核的調試了。
[從這節開始,側重於利用kgdb和source insight理解內核原理] [網上好像沒這個內容。只看源碼的話,由於source insight不能解析彙編源文件,在彙編源碼中定位到初始化的源頭好像很難,利用調試器很容易作到這點]
[待充實]
3G~4G虛擬地址空間的用途。(來自於qemu虛擬機的dmesg啓動信息,500m物理內存) <4>Zone PFN ranges: <4> DMA 0 -> 4096 <4> Normal 4096 -> 127984 <4> HighMem 127984 -> 127984 <6>virtual kernel memory layout: <4> fixmap : 0xfff4c000 - 0xfffff000 ( 716 kB) <4> pkmap : 0xff800000 - 0xffc00000 (4096 kB) <4> vmalloc : 0xe0000000 - 0xff7fe000 ( 503 MB) <4> lowmem : 0xc0000000 - 0xdf3f0000 ( 499 MB) <4> .init : 0xc037f000 - 0xc03bb000 ( 240 kB) <4> .data : 0xc02c0875 - 0xc03773ac ( 730 kB) <4> .text : 0xc0100000 - 0xc02c0875 (1794 kB) 3G~4G虛擬地址空間的用途。(來自於qemu虛擬機的dmesg啓動信息,897m物理內存) <4>Zone PFN ranges: <4> DMA 0 -> 4096 <4> Normal 4096 -> 229376 <4> HighMem 229376 -> 229616 <6>virtual kernel memory layout: <4> fixmap : 0xfff4c000 - 0xfffff000 ( 716 kB) <4> pkmap : 0xff800000 - 0xffc00000 (4096 kB) <4> vmalloc : 0xf8800000 - 0xff7fe000 ( 111 MB) <4> lowmem : 0xc0000000 - 0xf8000000 ( 896 MB) <4> .init : 0xc037f000 - 0xc03bb000 ( 240 kB) <4> .data : 0xc02c0875 - 0xc03773ac ( 730 kB) <4> .text : 0xc0100000 - 0xc02c0875 (1794 kB) 3G~4G虛擬地址空間的用途。(來自真機的dmesg啓動信息,3G物理內存) [ 0.000000] Zone PFN ranges: [ 0.000000] DMA 0 -> 4096 [ 0.000000] Normal 4096 -> 229376 [ 0.000000] HighMem 229376 -> 786416 [ 33.262853] virtual kernel memory layout: [ 33.262854] fixmap : 0xfff4b000 - 0xfffff000 ( 720 kB) [ 33.262855] pkmap : 0xff800000 - 0xffc00000 (4096 kB) [ 33.262856] vmalloc : 0xf8800000 - 0xff7fe000 ( 111 MB) [ 33.262857] lowmem : 0xc0000000 - 0xf8000000 ( 896 MB) [ 33.262858] .init : 0xc0421000 - 0xc047d000 ( 368 kB) [ 33.262859] .data : 0xc03204c4 - 0xc041bdc4 (1006 kB) [ 33.262861] .text : 0xc0100000 - 0xc03204c4 (2177 kB) top, 4G --->+-------------------+ | | | malloc()'ed memory| | interrupt stack | kernel | data | | text | kernel, 3G--->+-------------------+ | | | argv,envp | | user stack | | | | | | | | v | | | user process | ^ | | | | | | | | heap | | data | | text | user, 0G---> +-------------------+ Layout of virtual address space
咱們驗證一下用戶空間的內容(上圖的下部分)[未完,待續] 引用於http://linux.chinaunix.net/bbs/viewthread.php?tid=978491
查看進程的虛擬地址空間是如何使用的。 該文件有6列,分別爲: 地址:庫在進程裏地址範圍 權限:虛擬內存的權限,r=讀,w=寫,x=,s=共享,p=私有; 偏移量:庫在進程裏地址範圍 設備:映像文件的主設備號和次設備號; 節點:映像文件的節點號; 路徑: 映像文件的路徑 每項都與一個vm_area_struct結構成員對應, ---- struct vm_area_struct { struct mm_struct * vm_mm; /* The address space we belong to. */ unsigned long vm_start; /* Our start address within vm_mm. */ unsigned long vm_end; /* The first byte after our end address within vm_mm. */ /* linked list of VM areas per task, sorted by address */ struct vm_area_struct *vm_next; pgprot_t vm_page_prot; /* Access permissions of this VMA. */ unsigned long vm_flags; /* Flags, listed below. */ struct rb_node vm_rb; /* * For areas with an address space and backing store, * linkage into the address_space->i_mmap prio tree, or * linkage to the list of like vmas hanging off its node, or * linkage of vma in the address_space->i_mmap_nonlinear list. */ union { struct { struct list_head list; void *parent; /* aligns with prio_tree_node parent */ struct vm_area_struct *head; } vm_set; struct raw_prio_tree_node prio_tree_node; } shared; /* * A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma * list, after a COW of one of the file pages. A MAP_SHARED vma * can only be in the i_mmap tree. An anonymous MAP_PRIVATE, stack * or brk vma (with NULL file) can only be in an anon_vma list. */ struct list_head anon_vma_node; /* Serialized by anon_vma->lock */ struct anon_vma *anon_vma; /* Serialized by page_table_lock */ /* Function pointers to deal with this struct. */ struct vm_operations_struct * vm_ops; /* Information about our backing store: */ unsigned long vm_pgoff; /* Offset (within vm_file) in PAGE_SIZE units, *not* PAGE_CACHE_SIZE */ struct file * vm_file; /* File we map to (can be NULL). */ void * vm_private_data; /* was vm_pte (shared mem) */ unsigned long vm_truncate_count;/* truncate_count or restart_addr */ #ifndef CONFIG_MMU atomic_t vm_usage; /* refcount (VMAs shared if !MMU) */ #endif #ifdef CONFIG_NUMA struct mempolicy *vm_policy; /* NUMA policy for the VMA */ #endif
[todo 換個簡單的程序] $ ps -aux | grep firefox Warning: bad ps syntax, perhaps a bogus '-'? See http://procps.sf.net/faq.html fqh 8230 4.7 2.5 205872 80024 ? Tl 14:54 0:19 /usr/lib/firefox-3.0.1/firefox fqh 8313 0.0 0.0 3220 764 pts/1 R+ 15:01 0:00 grep firefox (gdb) attach 8230 ... ..... Loaded symbols for /usr/lib/libflashsupport.so Reading symbols from /usr/lib/libpulse.so.0...(no debugging symbols found)...done. Loaded symbols for /usr/lib/libpulse.so.0 Reading symbols from /lib/libcap.so.1...(no debugging symbols found)...done. Loaded symbols for /lib/libcap.so.1 (no debugging symbols found) 0xb7f24410 in __kernel_vsyscall () (gdb) bt #0 0xb7f24410 in __kernel_vsyscall () #1 0xb7d46c07 in poll () from /lib/tls/i686/cmov/libc.so.6 #2 0xb6b4e1c6 in ?? () from /usr/lib/libglib-2.0.so.0 #3 0xb6b4e74e in g_main_context_iteration () from /usr/lib/libglib-2.0.so.0 #4 0xb77ba87c in ?? () from /usr/lib/xulrunner-1.9.0.1/libxul.so #5 0xb77cf624 in ?? () from /usr/lib/xulrunner-1.9.0.1/libxul.so #6 0xb77cfa6f in ?? () from /usr/lib/xulrunner-1.9.0.1/libxul.so #7 0xb787ecd6 in ?? () from /usr/lib/xulrunner-1.9.0.1/libxul.so #8 0xb784e31f in ?? () from /usr/lib/xulrunner-1.9.0.1/libxul.so #9 0xb77cf75e in ?? () from /usr/lib/xulrunner-1.9.0.1/libxul.so #10 0xb765f122 in ?? () from /usr/lib/xulrunner-1.9.0.1/libxul.so #11 0xb70b3a88 in XRE_main () from /usr/lib/xulrunner-1.9.0.1/libxul.so #12 0x08049033 in ?? () #13 0xb7c90450 in __libc_start_main () from /lib/tls/i686/cmov/libc.so.6 #14 0x08048cc1 in ?? () (gdb) $ cat /proc/8230/maps 08048000-0804f000 r-xp 00000000 08:01 7022914 /usr/lib/firefox-3.0.1/firefox <-text,注意標誌 可讀可執行不可寫私有 0804f000-08050000 rw-p 00006000 08:01 7022914 /usr/lib/firefox-3.0.1/firefox <-data,注意標誌 可讀可寫不可執行 08050000-0abd4000 rw-p 08050000 00:00 0 [heap] <-heap,一共45.5多MB[todo:驗證向上增加]可讀可寫不可執行 ae060000-ae063000 r-xp 00000000 08:01 6941098 /usr/lib/libflashsupport.so <-libflashsupport.so 共享庫的代碼段, 可讀可執行不可寫 ae063000-ae064000 rw-p 00002000 08:01 6941098 /usr/lib/libflashsupport.so <-libflashsupport.so 共享庫的數據段, 可讀可寫不可執行 ..... .. b7f20000-b7f21000 rw-p 00001000 08:01 6942869 /usr/lib/libplds4.so.0d b7f21000-b7f22000 r--p 00000000 08:01 6966184 /usr/lib/locale/zh_CN.utf8/LC_IDENTIFICATION b7f22000-b7f24000 rw-p b7f22000 00:00 0 b7f24000-b7f25000 r-xp b7f24000 00:00 0 [vdso] b7f25000-b7f3f000 r-xp 00000000 08:01 2326545 /lib/ld-2.7.so b7f3f000-b7f41000 rw-p 00019000 08:01 2326545 /lib/ld-2.7.so bfbcd000-bfc0a000 rw-p bffc3000 00:00 0 [stack] <-stack,不到0.24MB,可讀可執行不可寫[todo:驗證向下增加] [todo:驗證argv,envp] $
[擴展]
[結合source insight分析一個內核子系統的原理。源碼分析工具雖好,但倒是個死的東西,不能實時觀察數據的生成和變化。若是在內核運行的時候,搭配調試器來分析,這個過程必定很形象和有趣]
參考書籍:
UNIX Filesystems Evolution, Design, and Implementation.pdf :
站點:
Ext4 (and Ext2/Ext3) Wiki: http://ext4.wiki.kernel.org/index.php/Main_Page
Ext4 Development project: http://www.bullopensource.org/ext4/
ext2-devel maillist archive: http://sourceforge.net/mailarchive/forum.php?forum=ext2-devel
參考文章:
「Linux Filesystems in 21 days 45 minutes」 http://us1.samba.org/samba/ftp/cifs-cvs/ols2006-fs-tutorial-smf.pdf
http://www.ibm.com/developerworks/cn/linux/l-cn-systemtap3/index.html
http://www.ibm.com/developerworks/cn/linux/l-systemtap/index.html
http://sourceware.org/systemtap/tutorial/
http://sourceware.org/systemtap/wiki
ubuntu下的配置安裝: http://sourceware.org/systemtap/wiki/SystemtapOnUbuntu
內核文檔 sysrq.txt
linux內核測試指南 相關章節
如何參與 Linux 內核開發
http://wiki.zh-kernel.org/doc/howto
Linux內核代碼風格
http://wiki.zh-kernel.org/doc/codingstyle
Linux內核開發郵件客戶端資料
http://wiki.zh-kernel.org/doc/email-clients.txt
Linux內核補丁提交注意事項
基於git的Gentoo中文文檔開發流程
http://www.gentoo-cn.org/doc/zh_cn/git-howto.xml
mutt配置使用
http://hi.baidu.com/springtty/blog/item/e6b25ddbb52f51ddb7fd4805.html
http://www.kongove.cn/blog/?p=149
說明:這個補丁本是用web-gmail發的。後來發現web-gmail遇到一行字數很長的補丁時,會致使補丁格式錯誤。本人決定之後使用claws,看的是它有線索成組的功能和草稿上有字數的標尺。內核社區中使用的郵件客戶端大多數是mutt(並非專指發補丁)。發補丁的所用工具也多種多樣,有用各類郵件客戶端的(mutt,claws,kmail...),有使用git-send-email的,還有使用quilt的,真是打開眼界。
另外本人感受claws比Sylpheed要快上幾十倍。claws原本是Sylpheed的實驗版,後來獨立出來了。
補丁的任務
mm/oom_kill.c:badness()函數 /** * badness - calculate a numeric value for how bad this task has been * @p: task struct of which task we should calculate * @uptime: current uptime in seconds * @mem: target memory controller//<-Li Zefan大俠上次提交了一個補丁,去掉了badness()的這個參數, 可是忘了刪除該參數的說明了。如今的任務是提交補丁把它刪除掉 * ...省略 */ unsigned long badness(struct task_struct *p, unsigned long uptime) {
過程
1. 進入git樹 XXX@ubuntu:~$ cd /storage/linus-git/linux-2.6/ XXX@ubuntu:/storage/linus-git/linux-2.6$ 2. 更新git樹 XXX@ubuntu:/storage/linus-git/linux-2.6$ git-pull Already up-to-date. 3. 修改目標源碼 XXX@ubuntu:/storage/linus-git/linux-2.6$ vi mm/oom_kill.c 刪除掉那個參數說明後結束vi,返回到shell下 4. 製做補丁 XXX@ubuntu:/storage/linus-git/linux-2.6$ git-diff > ../oom_kill.patch 5. 還原git樹 XXX@ubuntu:/storage/linus-git/linux-2.6$ patch -p1 < ../oom_kill.patch -R patching file mm/oom_kill.c 還原的方法或者採用下面方式 XXX@ubuntu:/storage/linus-git/linux-2.6$ git-gui 界面出來後,點擊Branch->reset 6.點擊compose新建郵件 7. 點擊insert file 載入補丁文件 diff --git a/mm/oom_kill.c b/mm/oom_kill.c index 64e5b4b..460f90e 100644 --- a/mm/oom_kill.c +++ b/mm/oom_kill.c @@ -38,7 +38,6 @@ static DEFINE_SPINLOCK(zone_scan_mutex); * badness - calculate a numeric value for how bad this task has been * @p: task struct of which task we should calculate * @uptime: current uptime in seconds - * @mem: target memory controller * * The formula used is relatively simple and documented inline in the * function. The main rationale is that we want to select a good task 8. 補全其餘信息,好比 標題,to, cc等,還有信件內容 本例是: 標題起爲:[PATCH]mm/oom_kill.c: cleanup kerneldoc of badness() //爲討好Randy.Dunlap,特地寫了字眼kerneldoc,由於他是主管內核文檔的 to "Randy.Dunlap" <rdunlap@xenotime.net> //收件人是誰得根據補丁的性質查看內核源碼中的MAINTAINERS文件, //難以確認是誰時,能夠到linus-git的web-git下參看你修改文件的歷史記錄,看別人是發給誰的 cc linux-kernel@vger.kernel.org, //這個必定要有 linux-mm@kvack.org //修改文件所在的子系統的郵件列表,當收件人寫錯時, //子系統的頭目們可能會注意到和接受你的補丁 郵件自己處理後變成下面的格式 Paramter @mem has been removed since v2.6.26, now delete it's comment. //補丁的做用 Signed-off-by: your-name <your-address@gmail.com> //你的簽收 --- //三個'-',表示下面的內容是補丁了。應用補丁的工具會根據這個標誌提取補丁。 diff --git a/mm/oom_kill.c b/mm/oom_kill.c index 64e5b4b..460f90e 100644 --- a/mm/oom_kill.c +++ b/mm/oom_kill.c @@ -38,7 +38,6 @@ static DEFINE_SPINLOCK(zone_scan_mutex); * badness - calculate a numeric value for how bad this task has been * @p: task struct of which task we should calculate * @uptime: current uptime in seconds - * @mem: target memory controller * * The formula used is relatively simple and documented inline in the * function. The main rationale is that we want to select a good task 9. 而後發信 10. 等待回覆 Randy Dunla果真勤快,兩個小時不到就收到了他的信件 Acked-by: Randy Dunlap <rdunlap@xenotime.net> Thanks. 說明: Acked-by表示他認爲補丁正確,但並不本身接收。 維護者回復applied纔算是接受了。 固然若是獲得比較有威望的人acked-by,被接受的可能性就大大的提升了。--Li Yang大牛 看來,我把補丁的維護人搞錯了,由於Randy Dunlap並沒領個人情:( 注意,這個不能急。有的補丁或許是由於太微小,對方都沒回復你,其實他已經收錄了你的補丁。 到子系統樹向linus的git樹合併時就會看到你的補丁(一般會在LKML中有個集體通告,說明 這批補丁中包含了哪些內容。)。等一個多星期無妨。 後記:過了三四天後,收到了一封信以下。這是mm樹的郵件系統發來的。可見Randy Dunlap 把這個補丁提交給mm樹了。在mm樹通過驗證補丁正確後就會再匯合到linus的主線樹中。這是 補丁接受的一種方式。固然,我遇到的狀況有,子系統維護人回覆你applied to xx(樹), 而後該負責人就要求linus merge他的樹,這樣就收不到mm樹的通知信。甚至有時子系統維護 人接受補丁了都不吭一聲,而後補丁又是直接merge到主線樹中。補丁接受的流程大概就這樣了。 From: akpm@linux-foundation.org To: mm-commits@vger.kernel.org Cc: qhfeng.kernel@gmail.com, rdunlap@xenotime.net Subject: + mm-oom_killc-fix-badness-kerneldoc.patch added to -mm tree Date: Thu, 30 Oct 2008 14:47:53 -0700 The patch titled mm/oom_kill.c: fix badness() kerneldoc has been added to the -mm tree. Its filename is mm-oom_killc-fix-badness-kerneldoc.patch ....省略 The current -mm tree may be found at http://userweb.kernel.org/~akpm/mmotm/ ------------------------------------------------------ Subject: mm/oom_kill.c: fix badness() kerneldoc From: Qinghuang Feng <qhfeng.kernel@gmail.com> Paramter @mem has been removed since v2.6.26, now delete it's comment. Signed-off-by: Qinghuang Feng <qhfeng.kernel@gmail.com> Acked-by: Randy Dunlap <rdunlap@xenotime.net> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> --- ...補丁內容省略 _ Patches currently in -mm which might be from qhfeng.kernel@gmail.com are origin.patch mm-oom_killc-fix-badness-kerneldoc.patch linux-next.patch
Git 中文教程
http://www.linuxsir.org/main/doc/git/gittutorcn.htm
git使用小結
http://wangcong.org/blog/?p=307
學習 Git
內核git庫:
http://git.kernel.org/?p=linux/kernel/git
綜合類:
「understanding the linux kernel」
」linux kernel development「
「linux源代碼情景分析」
「Embedded.Linux.Primer.A.Practical.Real.World.Approach.」
「The_Linux_Kernel_Primer_A_Top_Down_Approach_For_x86_and_PowerPC_Architectures」
子系統類:
文件系統:
「UNIX Filesystems Evolution, Design, and Implementation」
「File System Forensic Analysis」
「Windows NT File System Internals」
內存管理:
「Understanding The Linux Virtual Memory Manager」
網絡系統:
「The Linux® Networking Architecture: Design and Implementation of Network Protocols in the Linux Kernel」
「Understanding.Linux.Network.Internals」
驅動開發:
「linux device drivers」
「Essential.Linux.Device.Drivers」
源碼自己及附帶文檔
參考文章:
IBM-Linux 相關專題 http://www.ibm.com/developerworks/cn/linux/ 「Debugging Kernel Modules with User Mode Linux」
http://www.linuxjournal.com/article/5749
「Debugging Memory on Linux」 http://www.linuxjournal.com/article/4681
「DDD—Data Display Debugger」 http://www.linuxjournal.com/article/2315
「Linux 系統內核的調試」 http://www.ibm.com/developerworks/cn/linux/l-kdb/
System Dump和Core Dump的區別 http://hi.baidu.com/iruler/blog/item/c203de3522ff398ea61e122c.html
http://www.linuxjournal.com/user/800887/track
http://www.linuxjournal.com/http://www.ibm.com/developerworks/cn/linux/l-devmapper/index.html
read 系統調用剖析 http://www.ibm.com/developerworks/cn/linux/l-cn-read/index.html
http://blog.chinaunix.net/u/4206/showart_501237.html
http://hi.baidu.com/linux%5Fkernel/blog/category/pci%C9%E8%B1%B8%C7%FD%B6%AF
http://wiki.jk2410.org/wiki/Main_Page
http://www.ibm.com/developerworks/cn/linux/l-cn-clocks/index.html
利用Vmware5.5.1 和 kgdb調試 x86平臺的kernel
http://blog.chinaunix.net/u/22617/showart_338509.html
Welcome to Linux From Scratch
http://www.linuxfromscratch.org/
Unreliable Guide To Locking
http://www.kernel.org/pub/linux/kernel/people/rusty/kernel-locking/index.html
How do I printk <type> correctly?
http://lkml.org/lkml/2008/10/23/132
KernelJanitors/Todo
http://kernelnewbies.org/KernelJanitors/Todo
Coccinelle - a Framework for Linux Device Driver Evolution
http://www.emn.fr/x-info/coccinelle/
linux論文 http://www.linuxsymposium.org
www.linuxsymposium.org/2006/linuxsymposium_procv2.pdf
www.linuxsymposium.org/2006/linuxsymposium_procv1.pdf
understanding the linux kernel 在線文檔
http://www.linux-security.cn/ebooks/ulk3-html/
Data Structures and Algorithms with Object-Oriented Design Patterns in C++/Java/C#/Python/Ruby/Lua/Perl/PHP
cpan設置 Going to read /home/fqh/.cpan/sources/modules/02packages.details.txt.gz Warning: Your /home/fqh/.cpan/sources/modules/02packages.details.txt.gz does not contain a Line-Count header. 是選取站點不可用形成的。 http://tech.foolpig.com/2008/10/22/cpan-error-modulelist/ 1.刪除掉.cpan 2.perl -MCPAN -e shell 或1,2步驟換爲o conf init命令 3.選了africa下的三個站點 4.cpan設置完後,reload index便可 5.列舉模塊m 6.查詢 d /模塊/ --- __attribute__((context(x,0,1))) means "you need not hold x before, but you will hold one more of x after". __attribute__((context(x,1,0))) means "you must already hold x, and you will no longer hold x after". __attribute__((context(x,1,1))) means "you must already hold x, and you will continue to hold x".