- linux核心調試指南
- 一些前言
- 作者前言
- 知識從哪裡來
- 為什麼撰寫本文檔
- 為什麼需要彙編級調試
- ***第一部分:基礎知識***
- 總綱:核心世界的陷阱
- 源碼閱讀的陷阱
- 代碼調試的陷阱
- 原理了解的陷阱
- 建立調試環境
- 發行版的選擇和安裝
- 安裝交叉編譯工具
- bin工具集的使用
- qemu的使用
- initrd.img的原理與制作
- x86虛拟調試環境的建立
- arm虛拟調試環境的建立
- arm開發闆調試環境的建立
- gdb基礎
- 基本指令
- gdb之gui
- gdb技巧
- gdb宏
- 彙編基礎--X86篇
- 使用者手冊
- AT&T彙編格式
- 内聯彙編
- 彙編與C函數的互相調用
- 調用鍊形成和參數傳遞
- C難點的彙編解釋
- 優化級别的影響
- 彙編基礎--ARM篇
- 使用者手冊
- 調用鍊形成和參數傳遞
- 源碼浏覽工具
- 調用圖生成工具
- find + grep
- wine + SI
- global
- Source-Navigator
- vim + cscope/ctags
- kscope
- lxr
- SI等與gdb的特點
- 調用鍊、調用樹和調用圖
- 理想調用鍊
- 函數指針調用
- 調用鍊的層次
- 非理想調用鍊
- 調用樹與調用圖
- 穿越盲區
- 穿越gdb的盲區
- 穿越交叉索引工具的盲區
- 工程方法
- bug 與 OOPS
- ***第二部分:核心分析***
- 調試相關子系統
- kgdb源碼分析
- sysrq
- oprofile
- kprobes
- 驅動分析
- 載入子產品符号
- seq_file.c的分析
- module.c的分析
- 中斷處理過程
- s3c24xx記憶體初始化分析
- 虛拟位址空間
- 使用者層的觀察窗
- 互動,從核心層分析
- 了解裝置模型
- 面向對象的實作
- 裝置模型的分層
- 外圍支援機制
- 檔案系統
- ***第三部分:其他工具***
- strace
- ltrace
- SystemTap
- MEMWATCH
- YAMD
- Magic SysRq
- 附錄:社群交流相關
- 更新檔送出相關文檔
- 更新檔制作與送出示範
- git使用
- 附錄:核心參考書籍文章
- 私人備忘
- 一些前言
linux核心調試指南
大海裡的魚有很多,而我們需要的是魚鈎一隻
本文檔由大家一起自由編寫,修改和擴充,sniper負責維護。引用外來的文章要注明作者和來處。本文檔所有指令都是在ubuntu/debian下的操作。選取的核心源碼從文檔開始編寫時最新的核心版本–2.6.26開始,而且會随着linux的更新而不斷更換新的版本。是以文檔的内容可能前後不一緻。相信大家有能力克服這個問題。
本文檔的字元圖示在linux環境下顯示正常,在window下顯示有細微的錯亂。
本文檔唯一的更新網址是:http://wiki.zh-kernel.org/sniper 轉載請保留此網址。
有任何建議請發郵件:[email protected]
有任何問題請到郵件清單提問:http://zh-kernel.org/mailman/listinfo/linux-kernel
一些前言
作者前言
一個人默默地敲打這篇文章也有段時間了。在這個過程裡,沒有收到任何的贊譽,也沒接到任何的闆磚,沒有任何的回報。就這麼敲打着,修理着。但是本人從沒懷疑這篇文檔的價值,這是因為,本人就是這篇文檔的親身收益者。在這裡把它“無私”奉獻出來,乃是出于對于某類同道者锲而不舍孜孜以求的“德性”的認同和“同情”,你的痛苦我表示感同身受,你的迷茫我願意一起分擔。一定有人能從個文檔受益,這便已讓我知足。其實,寫這個文檔并非是件苦差,而是字字都是有感而發的,不吐不快的結果。這裡的句句都是本人教訓和經驗的記錄。
談到調試器,世上存在兩種截然不同的看法。其中一種,是超級解霸的作者,他認為“程式不是寫出來的,好程式絕對是調試出來的”。對于這個觀點,雖然本人學識淺陋,也很崇拜“ ”他的為人,但是本人還是持着極不認同的态度。而第二種相反觀點的人,便是linux之父linus了。他認為調試器隻會“誤人子弟”,隻會導緻人們迷于表象而不去真正了解源碼本身。并以此為由,長期沒把kgdb内置到核心中。對于調試器調試bug會引入錯誤的修正這個觀點,我認為還是有點道理的。但是他以此為由而不把它集合到核心中,這個做法我就認為是毫無道理了。因為linus本人就說過:“我隻使用GDB,而且我總是并不把它作為調試器來使用,隻是将其作為一個可以用來分析程式的分解器來使用。”既然他可以這樣做,為什麼就認定他人使用gdb的目的一定就是用來調試bug而不是另有所用呢?本人之是以這樣說,這是因為本人正也是使用gdb主要是用來輔助分析核心代碼而不是主要用來調試錯誤的。這也正就是本文的主題。
世上從不缺少解決問題的答案,缺少的是解決問題的方法。現在,linux的世界裡已經不缺少牛書了,将盡一千頁一本的滿載答案的磚頭書接踵而來,但是漸漸地發現,看書看到後面就忘了前面,回到前面有忘了後面,甚至一個章節還沒看完,那個子系統已經被完全重寫了。慢慢地,就會懷疑“我是不是真的變老了?真的不行了?”但是我們從沒想過:“憑什麼我們就如此受制于人?他就能搞懂,而我就不行呢?”。其實,我們需要的是一種重其意而忘其形的根本之道,需要的是一種兵來将擋,火來水淹的通用解決方法。而絕不是淹沒于牛人們的結論中。否則,遇到一個新的問題,就隻能埋怨牛人的書還不夠厚,以至于沒把你需要的東西也包括進去了。牛人一定有一套牛方法,而他在書中不詳說,我不認為是他故意“留一手”,而是認為這是對自身覺得習以為常的事物的一種疏忽。牛人的研究結果其實不是最重要的,他的研究方法和手段才是最重要的事情。而我,也漸漸地發現,調試器能帶給我們很多有用的提示,使得我們能不斷的尋找到思考的靈感和方向,也使得學習變得非常的有趣性和有目的性。我想,利用調試器輔助源碼分析,是不是正是很多牛人正在做的而沒有說出來的事情呢?無論答案如何,本人還是覺得,調試器是個好東西,不要輕易把它擱置在一旁。雖然很多高人也許已經是深安此道,甚至已經不需要它的提示了,但是它依然有益于我等功力尚淺的人。把這種經驗和技巧記錄下來,讓需要這項技巧的人少化時間去摸索,這絕對不是一件壞事。
正是因為這個原因,随着文檔慢慢地變大,也更加的覺得文檔的題目起得有點不恰當了,題目起作“核心動态分析指南”更恰當點。文檔的主旨是利用調試器動态分析核心,調試錯誤隻是這個過程的副産品罷了。不過,這個新的名字實在是不夠現在名字“刺眼”,是以也就沒有啟用它。
說了這麼多的廢話和出格的話,無非是有兩個目的:這個文章慢慢的變得這麼長了,如果沒有半句的“人”話,沒有半句的現實世界中的語句。那估計本人不是變成了機器人,閱讀的人也會變成了機器人。順便借這段文字交交朋友。另一個目的呢,是說不應拘束于工具,工具是死的,人是活的。如果某些工具确能帶給我們某些有益的提示,我們就可以去嘗試它,取起優點而舍其糟粕。
引用的原文:
Linus 談調試器和核心如何發展: http://www.bitscn.com/linux/kernel/200604/7493.html
知識從哪裡來
1. 永遠不要忘記的三大幫助指令
- XXX -h(xxx –help)
- man -a XXX
- info XXX
2. 如何安裝幫助文檔
- $ sudo synaptic 界面出來後,在“組别”->“文檔”選取你要的文檔進行安裝
- 或$ apt-cache search Documentation | grep XXX 搜尋需要的文檔進行安裝
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. 擷取核心文檔
- 源碼本身
- 源碼中的注釋
- 核心源碼附帶的文檔 Documentation
- 相關的教科書
- 論文 免費論文引擎 http://citeseerx.ist.psu.edu/
- 核心子系統的官方網站
- 擷取核心源碼目錄Documentation/DocBook/ 下已經編譯好的書籍
找到最新版本的文檔 $ 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對核心進行源碼級别和彙編級别的觀察和調試。
而這種調試的目的有兩個:
- 确定bug産生的引入點。這部分内容放于本文檔第一部分。
- 配合源碼閱讀工具(source insight,kscope等),觀察核心實時運作的狀況,觀察核心資料的産生和變化,以及觀察各個函數的動态調用關系,進而以一種精确的動态的和驗證性的方式來了解核心運作的原理。這部分内容放于本文檔第二部分
前者是調試器應用的主要價值,而後者卻是本文檔的興趣所在。
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源碼調試這個級别下給出的資訊是無法了解的,甚至看起來是錯誤的。但是如果直接對調試器給出的反彙編代碼進行分析,就不會受到那類問題的束縛。也就是說,進行彙編級别的調試能最大程度的利用調試器的功能。
- 彙編是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
建立調試環境
發行版的選擇和安裝
為什麼選debian
[如題] 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大的工具庫中尋找你想要的工具。在結果傳回後,選擇好你的工具,再點點一個叫做“應用”的小方塊,過一會,就可以使用你的工具了。
再也沒有了“缺少什麼什麼包”的煩人提示了,一切都這麼簡單,又這麼強大。這,正是我想要的。
debian與ubuntu
[兩者差別,版本外号,支援社群,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版本下的操作。
從0安裝debian
[如果想領教古典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. :)
debian重要指令
[來源]《APT and Dpkg 快速參考表》 http://i18n.linux.net.cn/others/APT_and_Dpkg.php
Apt 不止是 apt-get
http://www.erwinwang.com/node/10
中文環境設定
debian的鍵盤設定更改
預設安裝的debian,鍵盤的設定可能有問題。比如“|”打不出來。值得一提的是,這個設定甚至是和qemu的monitor模式相關聯的。也就是說,qemu下有的字元也打不出來。如果有這個問題,按下面步驟設定
System→Preferences→Keyboard→Layouts
然後通過“Add”增加China,并設定它為預設,或者同時把其他的删除掉。
英文Locale下使用中文輸入法
說明,中文環境比英文環境有很多缺點。比如編譯時編譯器的提示都給漢化了,有如,minicom的中文漢化界面是錯亂的,而且minicom無法設定。本人一般是英文環境+中文輸入法。先安裝好好中文環境,系統中就有了中文輸入法和其他一些和中文有關的東西。然後轉到英文環境下,按照下面做法更改scim的配置檔案即可。
來自:http://wiki.ubuntu.org.cn/index.php?title=%E8%8B%B1%E6%96%87Locale%E4%B8%8B%E4%BD%BF%E7%94%A8%E4%B8%AD%E6%96%87%E8%BE%93%E5%85%A5%E6%B3%95&variant=zh-cn
編輯 /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" 注意,一定要重新開機一下機器。
pdf亂碼的解決
$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.
安裝arm-linux-gnueabi-XXX 工具集
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
什麼是EABI
答: 來自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 工具集
注: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,看腳本的内容,
bin工具集的使用
[該怎麼稱呼這類工具?待詳述]
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-linux-gnueabi-gcc
檢視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
注意它的預設選項設定
$ 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").
qemu的使用
參考手冊
http://bellard.org/qemu/user-doc.html
http://wiki.debian.org.tw/index.php/QEMU
http://www.h7.dion.ne.jp/~qemu-win/
http://bellard.org/qemu/
郵件清單
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久終于裝好了,現在可以啟動試試: [[email protected] 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可以檢視其相關說明
initrd.img的原理與制作
[擴充,原理,相關指令。下面的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
x86虛拟調試環境的建立
參考
“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和核心内置kgdb
缺點:相對于下節的“基于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
基于qemu和qemu内置gdbstub
- 參考文章
“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: [email protected]:/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的 方法就可以從第一個機器指令開始進行。 [email protected]:/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)
arm虛拟調試環境的建立
利用qemu
利用qemu安裝debian linux
目标:
本節在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 安裝完成後,它提示你把CD光牒拿掉并重新開機系統時,終止掉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
http://www.aurel32.net/info/debian_arm_qemu.php
利用qemu安裝能進行核心調試的系統
[暫時沒法子,期待擴充。下面這個例子可以,但沒嘗試。估計這個方法與下節的利用skyeye的方法相比,沒有優勢。因為這個方法可能也是不能進行全程調試。但是下面網站的資料還是有一定參考價值的。]
使用qemu-jk2410做為學習環境:
http://wiki.jk2410.org/wiki/%E4%BD%BF%E7%94%A8qemu-jk2410%E5%81%9A%E7%82%BA%E5%AD%B8%E7%BF%92%E7%92%B0%E5%A2%83
另外:看看下面這個站點,
Firmware Linux: http://landley.net/code/firmware/
利用skyeye
skyeye虛拟機的核心調試
相對于利用qemu的方式,用skyeye虛拟機調試核心有個很重要的
優點是:
調試可以從第一條機器指令開始。這對研究系統啟動過程提供了極大的便利。
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
為s3c2410配置2.6.26核心
[啟動過程中有若幹錯誤提示,但核心能啟動成功并運作。有待研究]
目标:
得到一個基于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.啟動虛拟機
[email protected]:~/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: [email protected]:~/桌面/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: [email protected]:~/桌面/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)
使用最新的skyeye
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出錯,你就得去查查那裡,并手動修改處理一下即可。
arm開發闆調試環境的建立
基于序列槽
為qq2440平台移植2.6.26或更新核心,并建立kgdb調試環境
進行中...
[移植中的一些零碎的筆記]
1.核心版本
使用linus的git,但是已知2.6.25中arm已經支援kgdb了。
[email protected]:/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 . . . . . . . . . :
開發闆的網絡資訊:
[[email protected](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:開發闆中的挂載點
[[email protected](none) /]# mount -t nfs -o nolock 192.168.1.111:/new/root_nfs /tmp/fuck
[[email protected](none) /]# cd /tmp/fuck/
[[email protected](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 ([email protected]) (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基礎
基本指令
推薦這篇,内容很全: gdb 使用手冊 http://blog.chinaunix.net/u/11240/showart.php?id=340632
終極參考: Debugging with GDB http://sourceware.org/gdb/current/onlinedocs/gdb.html#SEC_Top
gdb之gui
網址:
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一般用來調試了解核心原理。
gdb技巧
另外有用的指令 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 *[email protected] $83 = {{flags = 1024, _count = {counter = 1}, {_mapcount = {counter = -1}, {inuse = 65535, objects = 65535}}, {{private = 0, mapping = 0×0}, ptl =…
用ddd的圖形顯示指令是 (gdb) graph display *[email protected]
參考 p *[email protected]
@的左邊是數組的首位址的值,也就是變量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宏
本小節意義:為了友善把調試内容複制出來,而又需要一定的功能,本人經常使用的工具是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/
gdb宏的使用
假設要使用下節的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 <[email protected]> # V Srivatsa <[email protected]> # Maneesh Soni <[email protected]> # 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 <[email protected]> 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
彙編基礎--X86篇
注意:某些内容不具備普遍性。比如給出的反彙編代碼,在不同的優化等級下是不同的。但是在熟悉了典型的函數調用鍊反彙編代碼,對于有變化的其他形式也就不難了解了。
使用者手冊
Intel® 64 and IA-32 Architectures Software Developer’s Manuals
http://www.intel.com/products/processor/manuals/index.htm
AT&T彙編格式
參考
“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
彙編與C函數的互相調用
調用鍊形成和參數傳遞
參考文章 [多如牛毛]
“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 callee
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.
待解釋
C難點的彙編解釋
例1
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 <[email protected]> //調用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 <[email protected]> 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 <[email protected]>
上面的代碼指令根本就沒有機會運作。
結論,一個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; }
例2
短路邏輯算法。
這樣的例子在核心代碼中也是非常地多,一般用在短的函數或宏中。
#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 <[email protected]> //調用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; ..... }
例3
自增自減
自增自減,以及增減的前後問題。這類代碼在核心數不勝數。了解稍有偏差,就會産生“邊界問題”,或者在條件判斷時了解出錯。
#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 <[email protected]> 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 <[email protected]> 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; }
例14
函數指針
解釋在“穿越交叉索引工具的盲區”→函數指針
#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 .....省略 } [email protected]:~/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選擇編譯器的優化級别。優化級别分别有
- -O0: 除一些簡單的代碼編号外,關閉所有優化,該選項可提供最直接的優化資訊。
- -O1: 關閉嚴重影響調試效果的優化功能。使用該編譯選項,編譯器會移除程式中未使用到的内聯函數和靜态函數。如果于–debug(也就是-g)一起使用,該選項可以在較好的代碼密度下,給出最佳調試視圖。
- -O2: 生成充分優化代碼。如果與–debug一起使用,調試效果可能不令人滿意,因為對目标代碼到源代碼的映射可能因為代碼優化而發生變化。如果不生成調試表,這是預設優化級别。
- -O3: 最高優化級别。使用該優化級别,使生成的代碼在時間和空間上尋求平衡。
例子
#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 <[email protected]> //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 <[email protected]> //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 <[email protected]> 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 <[email protected]> //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 <[email protected]> //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 <[email protected]> 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 <[email protected]> 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 <[email protected]> //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 <[email protected]> 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 <[email protected]> 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 <[email protected]> //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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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
彙編基礎--ARM篇
說明:
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
指令速查 http://www.arm.com/pdfs/QRC0001H_rvct_v2.1_arm.pdf
調用鍊形成和參數傳遞
注意: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。選用哪個 别名,是依賴于不同平台的選擇。
[擴充,簡要說明原理。并用執行個體解析]
寄存器的角色與保護
- 寄存器的角色(AAPCS标準)
寄存器 | 可選寄存器名 | 特殊寄存器名 | 在函數調用中的角色 |
---|---|---|---|
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,這是應用層的普通函數調用過程。如果是遠調用,跨态調用要考慮的東西更多。但這個例子已經充分展示了調用過程的繁複部分。
- 函數調用前調用者的動作
- 函數調用 call 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
對于源碼的閱讀工具,一般是選取後面提到的某種源碼索引工具,再和find以及grep“高低搭配”一起來使用。
1.指令選項
2.正規表達式
Regular Expression HOWTO: http://www.amk.ca/python/howto/regex/
正規表達式之道: http://net.pku.edu.cn/~yhf/tao_regexps_zh.html
wine + SI
wine + source insight
優缺點
優點: SI的特點是有圖形界面,操作和浏覽特别友善快捷。特别是它的“函數調用樹”的圖形顯示功能,以及分視窗自動顯示函數,變量等定義的功能。
缺點: 不能解析彙編源檔案。
安裝wine
在ubuntu/debian下用以下指令就能線上安裝wine
$ sudo apt-get install wine
安裝好後,就能看到wine的快捷菜單被添加到了工作列的“應用程式”中。
安裝SI
wine安裝好後,就可以像在windows一樣去安裝使用SI了。安裝完成後,SI的快捷菜單被添加到“應用程式”→“wine”→“programs”→“source insight3”中。以後用快捷菜單就能啟動SI
SI的使用
可以亂點亂試一下,它能提供很多的功能。其中一些經常要到的功能有 查找符号;函數調用的函數,被調用的函數;以及調用關系的多層展開顯示;字元串搜尋等。
global
[待玩] http://www.gnu.org/software/global/
Source-Navigator
[待玩] http://sourcenav.sourceforge.net/
安裝:
在ubuntu下可以線上安裝
$ sudo apt-get install sourcenav
運作:
$ snavigator
vim + cscope/ctags
參考:
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
http://dev.21tx.com/2007/02/21/10252.html
優缺點
優點: 本人感覺在終端下看源碼比較舒服。
缺點: 沒有一個實時顯示函數/變量定義的分視窗。也不能直接顯示“調用樹”,但有其他小工具可以實作該功能。也許vim高手能解決這些問題。
安裝cscope/ctags
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下導入索引
$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
kscope是cscope的圖形前端工具。在ubuntu下可以線上安裝。它的界面上和操作上與source insight都比較類似。但是目前它對cpu的占用很大,不是很好。但是它和cscope相比,有一個很大的優點是:可以圖形顯示“函數調用樹”,甚至這個功能比SI還強大。
$sudo apt-get install kscope
lxr
1. 優缺點
優點:本身好像沒什麼特别的優點。但是有專門提供這種服務的網站,上面有很多不同系統的不同版本源碼
缺點:在本機上配置運作的話,配置麻煩。如果是浏覽lxr站點的方式,速度比較慢。
2. lxr官方: http://lxr.linux.no/
特點是可以浏覽曆史上linux所有版本的源碼,可以看到它的演化過程。
3. 其他系統的源碼 http://fxr.watson.org/
估計超一流的核心開發人員,可能會經常通路此類站點。因為他需要借鑒其他系統的設計思想。
SI等與gdb的特點
在源碼閱讀的功能上:
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 * )[email protected] $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的盲區
程序切換
中斷異常
系統調用
穿越交叉索引工具的盲區
函數指針
該小節内容移到了: 調用鍊的狀态→函數指針調用
檢視函數的參數
我們知道,一個函數的計算結果并不都是通過它的傳回值傳回的,有時會通過函數的參數傳回真正感興趣的資料。看核心源碼的時候,如果調用鍊過長,涉及内容和資料結構過多的話,往往是看到最後都記不住函數的參數哪些是已經“初始化的”。
這也是交叉索引工具無法克服的先天弱點。它能動态索引源碼,卻無法動态檢視資料。此時,可以利用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 與 OOPS
[主要研究定位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
[參考文章]有的已過時,而且深度不夠。
網站
http://bugzilla.kernel.org/
http://www.kerneloops.org/
http://www.lkml.org/ 搜尋bug
***第二部分:核心分析***
這部分的内容側重于核心原理分析,其中涉及gdb調試器的内容不是很多,但它起的作用很關鍵,主要用于觀察核心資料的生成及變化,在對源碼了解有困惑時用于驗證自己的猜想。另外,gdb一個很重要的功能是,攔截通過函數指針調用的函數,進而追溯整個調用鍊,交叉索引工具無法做到這點。
另外,調試核心時,利用gdb的“list 函數名”指令看到的C代碼都是目前處理器目前配置下核心實際運作的函數版本:”disass 函數名”看到的都是處理器實際運作時的機器代碼,也就是說define語句和inline函數已經被編譯器處理了,而且編譯器也完成了優化。是以,gdb本身就是一種不可替代的源碼浏覽工具,它能篩選掉出實際運作的函數版本,又能呈現出實際運作的機器碼。
調試相關子系統
kgdb源碼分析
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
sysrq
oprofile
kprobes
驅動分析
[分析一個簡單的驅動,觀察函數調用流程。重點觀察驅動與驅動模型,以及和系統核心的互動過程。比如,中斷的整個生命周期。]
參考:
“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)
然後,餘下的對子產品的調試就類似對核心的調試了。
seq_file.c的分析
module.c的分析
中斷處理過程
s3c24xx記憶體初始化分析
[從這節開始,側重于利用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分析一個核心子系統的原理。源碼分析工具雖好,但卻是個死的東西,不能實時觀察資料的生成和變化。如果在核心運作的時候,搭配調試器來分析,這個過程一定很形象和有趣]
面向對象的實作
裝置模型的分層
外圍支援機制
sysfs
hotplug
檔案系統
參考書籍:
UNIX Filesystems Evolution, Design, and Implementation.pdf :
http://www.google.cn/search?q=UNIX+Filesystems+Evolution%2C+Design%2C+and+Implementation&ie=utf-8&oe=utf-8&aq=t&rls=com.ubuntu:zh-CN:unofficial&client=firefox-a
站點:
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
***第三部分:其他工具***
strace
- 作用: strace能攔截和記錄應用程式發起的系統調用和它收到的信号。主要用于觀察應用層和核心層的互動。
- 指令選項: 檢視,$strace –help 或$man strace 或 $info strace
- 執行個體
ltrace
- 作用: ltrace用于監控程式發起的庫函數調用以及程式收到的信号。
SystemTap
- 動态收集Linux核心資訊和性能資料
- 官方 http://sourceware.org/systemtap/
- 參考文章
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
MEMWATCH
- 作用: 跟蹤程式中的記憶體洩漏和錯誤
YAMD
- 作用: 查找 C 和 C++ 中動态的、與記憶體配置設定有關的問題
Magic SysRq
核心文檔 sysrq.txt
http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=blob;f=Documentation/sysrq.txt;h=10a0263ebb3f01e832c7827cc75d7fe54b341a6f;hb=f8d56f1771e4867acc461146764b4feeb5245669
linux核心測試指南 相關章節
http://wiki.zh-kernel.org/#%E6%96%87%E7%AB%A0
附錄:社群交流相關
更新檔送出相關文檔
如何參與 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核心更新檔送出注意事項
http://wiki.zh-kernel.org/doc/linux%E5%86%85%E6%A0%B8%E8%A1%A5%E4%B8%81%E6%8F%90%E4%BA%A4%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9
基于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
http://www.kongove.cn/blog/?p=229
http://www.kongove.cn/blog/?p=225
更新檔制作與送出示範
說明:這個更新檔本是用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樹 [email protected]:~$ cd /storage/linus-git/linux-2.6/ [email protected]:/storage/linus-git/linux-2.6$ 2. 更新git樹 [email protected]:/storage/linus-git/linux-2.6$ git-pull Already up-to-date. 3. 修改目标源碼 [email protected]:/storage/linus-git/linux-2.6$ vi mm/oom_kill.c 删除掉那個參數說明後結束vi,傳回到shell下 4. 制作更新檔 [email protected]:/storage/linus-git/linux-2.6$ git-diff > ../oom_kill.patch 5. 還原git樹 [email protected]:/storage/linus-git/linux-2.6$ patch -p1 < ../oom_kill.patch -R patching file mm/oom_kill.c 還原的方法或者采用下面方式 [email protected]:/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" <[email protected]> //收件人是誰得根據更新檔的性質檢視核心源碼中的MAINTAINERS檔案, //難以确認是誰時,可以到linus-git的web-git下參看你修改檔案的曆史記錄,看别人是發給誰的 cc [email protected], //這個一定要有 [email protected] //修改檔案所在的子系統的郵件清單,當收件人寫錯時, //子系統的頭目們可能會注意到和接受你的更新檔 郵件本身處理後變成下面的格式 Paramter @mem has been removed since v2.6.26, now delete it's comment. //更新檔的作用 Signed-off-by: your-name <[email protected]> //你的簽收 --- //三個'-',表示下面的内容是更新檔了。應用更新檔的工具會根據這個标志提取更新檔。 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 <[email protected]> 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: [email protected] To: [email protected] Cc: [email protected], [email protected] 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 <[email protected]> Paramter @mem has been removed since v2.6.26, now delete it's comment. Signed-off-by: Qinghuang Feng <[email protected]> Acked-by: Randy Dunlap <[email protected]> Signed-off-by: Andrew Morton <[email protected]> --- ...更新檔内容省略 _ Patches currently in -mm which might be from [email protected] are origin.patch mm-oom_killc-fix-badness-kerneldoc.patch linux-next.patch
git使用
Git 中文教程
http://www.linuxsir.org/main/doc/git/gittutorcn.htm
git使用小結
http://wangcong.org/blog/?p=307
學習 Git
http://www.zeuux.org/science/learning-git.cn.html
附錄:核心參考書籍文章
核心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
http://www.brpreiss.com/books/opus4/
ftp://ftp.akaedu.org/../1.html
私人備忘
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".
1) WarStories