My code works, I don’t know why.

國王的耳朵是驢耳朵

Vim POSIX Group Regex 使用方式

| Comments

由於個人需求,需要在vim下面使用稍微複雜的字串搜尋取代。故整理這篇以後可以參考。

目錄

測試環境

1
2
3
4
5
6
7
8
9
$ lsb_release -a
No LSB modules are available.
Distributor ID:   Ubuntu
Description:  Ubuntu 18.04.1 LTS
Release:  18.04
Codename: bionic

$ vim --version
VIM - Vi IMproved 8.0 (2016 Sep 12, compiled Apr 10 2018 21:31:58)

問題描述以及POSIX regex grouping 簡介

Regular express 的特色是他可以match不同的pattern,所以用於搜尋和取代是非常的方便,然而當要把符合條件的字串前面或後面加上字串就會有一個問題,那就是符合的字串要怎麼表示?舉例來說,當我們想要在下面log[mem....] 之後放入test,要怎麼做到? 這時候我們就可以使用reguler expressiongroup 功能了

1
2
3
[    0.000000] BIOS-e820: [mem 0x0000000000000000-0x0000000000057fff] usable
[    0.000000] BIOS-e820: [mem 0x0000000000058000-0x0000000000058fff] reserved
[    0.000000] BIOS-e820: [mem 0x0000000000059000-0x000000000009dfff] usable

參考語法

  • 指定group,一組regex可以指定零到多個group
    • \(match_patter\)
  • 取值
    • \0
      • 前項所有的group
    • \1
      • 第一組group
    • \2
      • 第二組group
    • \3
      • 第三組group
    • 千秋萬世直到永遠

範例

就用上面的訊息當範例吧

1
2
3
[    0.000000] BIOS-e820: [mem 0x0000000000000000-0x0000000000057fff] usable
[    0.000000] BIOS-e820: [mem 0x0000000000058000-0x0000000000058fff] reserved
[    0.000000] BIOS-e820: [mem 0x0000000000059000-0x000000000009dfff] usable

範例一: 在[mem …]之後插入test

  • 指令: :%s/\(\[mem.*\]\)/\1 test/g
1
2
3
[    0.000000] BIOS-e820: [mem 0x0000000000000000-0x0000000000057fff] test usable
[    0.000000] BIOS-e820: [mem 0x0000000000058000-0x0000000000058fff] test reserved
[    0.000000] BIOS-e820: [mem 0x0000000000059000-0x000000000009dfff] test usable

範例二: 設定三組group,都插入test

  • 指令: :%s/\(^\[.*\]\) \(\BIOS-e820:\) \(\[mem.*\]\)/\1 test1 \2 test2 \3 test3 /g
1
2
3
[    0.000000] test1 BIOS-e820: test2 [mem 0x0000000000000000-0x0000000000057fff] test3  usable
[    0.000000] test1 BIOS-e820: test2 [mem 0x0000000000058000-0x0000000000058fff] test3  reserved
[    0.000000] test1 BIOS-e820: test2 [mem 0x0000000000059000-0x000000000009dfff] test3  usable

給自己剪貼用的vim設定

| Comments

分享使用vim 的心得,加上使用Vundle plugin管理工具功能配合外部程式碼分享軟體cscopectags來trace C語言的程式碼以及編輯Python程式碼相關設定。

  • 致謝,感謝網友Scott介紹vim register概念,葉闆介紹的tagbar,和Kyle Lin介紹的airline。

目錄

測試環境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ lsb_release  -a
No LSB modules are available.
Distributor ID:   Ubuntu
Description:  Ubuntu 16.04.3 LTS
Release:  16.04
Codename: xenial


$ ctags --version
Exuberant Ctags 5.9~svn20110310, Copyright (C) 1996-2009 Darren Hiebert
...

$ cscope --version
cscope: version 15.8b

$ vim --version
VIM - Vi IMproved 7.4 (2013 Aug 10, compiled Nov 24 2016 16:44:48)
Included patches: 1-1689
...

設定.vimrc以及Vundle plugins

事前準備

您需要確認

  • vim版本為7.4以上
  • 安裝ctags和cscope,指令如下 sudo apt-get install exuberant-ctags cscope

安裝Vundle

Vundle是vim plugin 管理工具,他可以透過URL, github, 以及local FS等方式安裝甚至更新Plugin。類似的工具還有不少,我只是挑看到的第一個而已。

Vundle常用的指令如下,還蠻容易望文生義所以我就不解釋了

  • :PluginList
  • :PluginInstall
  • :PluginClean
  • :PluginUpdate

安裝方式如下

  • 首先你要下載Vundle,指令如下 git clone https://github.com/VundleVim/Vundle.vim.git ~/.vim/bundle/Vundle.vim

  • 接下來在你的.vimrc加入下面這段,我是從官方網頁改的,其實只是把他的範例Plugin幹掉並加上分隔線及分隔線內的註解而已

.vimrc 要加的部份
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
"====================================================================
" Start vundle
"====================================================================
set nocompatible              " be iMproved, required
filetype off                  " required

" set the runtime path to include Vundle and initialize
set rtp+=~/.vim/bundle/Vundle.vim
call vundle#begin()

" alternatively, pass a path where Vundle should install plugins
"call vundle#begin('~/some/path/here')

" let Vundle manage Vundle, required
Plugin 'VundleVim/Vundle.vim'

"===============================================================
" Write your plugins here
"===============================================================
Plugin 'Yggdroot/indentLine'

"====================================================================
" Run vundle
"====================================================================
" All of your Plugins must be added before the following line
call vundle#end()            " required
filetype plugin indent on    " required
" To ignore plugin indent changes, instead use:
"filetype plugin on
"
" Brief help
" :PluginList       - lists configured plugins
" :PluginInstall    - installs plugins; append `!` to update or just :PluginUpdate
" :PluginSearch foo - searches for foo; append `!` to refresh local cache
" :PluginClean      - confirms removal of unused plugins; append `!` to auto-approve removal
"
" see :h vundle for more details or wiki for FAQ
" Put your non-Plugin stuff after this line

注意下面列出的這幾行statements,你要新增或移除Plugin就是改這個地方。這些Plugin將會在後面介紹。剛好我要安裝的Plugin都是在GitHub上開發或有mirror。而Vundle可以用直接指定Plugin 專案在GitHub相對路徑即可安裝。這些描述也是Vundle載入Plugin 的順序,沒寫對順序有可能有相依問題請自行注意。

例如https://github.com/Yggdroot/indentLine 就寫成Yggdroot/indentLine

我安裝的Plugin
1
2
3
4
5
6
7
8
9
10
11
12
13
"===============================================================
" Write your plugins here
"===============================================================
Plugin 'Yggdroot/indentLine'
Plugin 'ntpeters/vim-better-whitespace'
Plugin 'vim-airline/vim-airline'
Plugin 'tpope/vim-fugitive'
Plugin 'chazy/cscope_maps'
Plugin 'vim-scripts/taglist.vim'
Plugin 'scrooloose/nerdtree'
Plugin 'wesleyche/SrcExpl'
Plugin 'wesleyche/Trinity'
Plugin 'majutsushi/tagbar'
  • 確定新增/刪除Plugin後,就可以執行vim/gvim,使用下面命令
    • :PluginInstall
    • :PluginClean

我安裝的Vundle Plugins

因為安裝方式已經在上面了,這邊就以介紹為主

編輯器相關

airline

安裝準備

先看圖,圖中最下方的那行就是airline,可以顯示一些有用的資訊

由左到右我們可以看到Vim 模式,Git branch 等資訊。以及一些比較特別的符號,這表示我們需要

  • 讓airline取得git資訊
  • 讓airline取得特別符號

也就是說,在安裝airline前要做一些前置動作如下

  • 讓airline取得git資訊
    • 很簡單,安裝vim-fugitive plugin即可
  • 讓airline取得特別符號 這也不難,就是安裝特殊字型,並且設定GUI時存取這些字型。方式如下

取得字型

1
git clone https://github.com/powerline/fonts

安裝字型

1
cd fonts && ./install.sh

.vimrc中指定安裝的字型

1
set guifont=Inconsolata\ for\ Powerline\ 20

設定airline

把下面的資料放入.vimrc即可

1
2
let g:airline_powerline_fonts = 1
set laststatus=2

indentLine

當Ident為空白增加以下的Indent 對齊參考資線

注意此Plugin在Ident為tab同時又加上顯示tab字元時自動失效,目前workaround就是顯示tab字元為|,接下來以.延伸作為辨別。範例如下:

vim-better-whitespace

trailing space顯示成明顯的紅色

Trace 程式碼相關

cscope_maps

簡單來說,就是把cscope指令對應到Hot key

先列出find部份的指令

1
2
3
4
5
6
7
8
9
find : Query for a pattern            (Usage: find c|d|e|f|g|i|s|t name)
       c: Find functions calling this function
       d: Find functions called by this function
       e: Find this egrep pattern
       f: Find this file
       g: Find this definition
       i: Find files #including this file
       s: Find this C symbol
       t: Find this text string

他的使用方法也很簡單,就是先把游標移動到你要查的statement,再按ctrl + \ + c|d|e|f|g|i|s|t 其中一個

舉例來說,我在下圖中把游標移動到core_sys_select函數後按下ctrl + \ + c的結果如下

SrcExpl

當啟動時,您的游標在那個敘述,Source explorer 會切割視窗,印出該敘述的定義。舉例來說,當我游標在138行的free_poll_entry的話,顯示的畫面如下。

taglist

列出目前檔案所有symbol並且可以選擇symbol切換到該symbol在檔案中的位置

nerdtree

以樹狀顯示目前檔案所在目錄結構,看圖就知道

Trinity

看完以上三個,你可能會覺得奇怪好像沒提到怎麼啟動。這就是Trinity大顯身手的地方了。你安裝Trinity後,再Vundle後面加上下面的敘述就可以有

  • F8 : 同時打開或關閉nerdtree, Source explorer, 以及tag list
  • F9 : 打開或關閉Source explorer
  • F10: 打開或關閉tag list
  • F11: 打開或關閉nerdtree
.vimrc 的Trinity設定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
"====================================================================
" Trinity Settings
"====================================================================
" Open and close all the three plugins on the same time
nmap <F8>  :TrinityToggleAll<CR>

" Open and close the Source Explorer separately
nmap <F9>  :TrinityToggleSourceExplorer<CR>

" Open and close the Taglist separately
nmap <F10> :TrinityToggleTagList<CR>

" Open and close the NERD Tree separately
nmap <F11> :TrinityToggleNERDTree<CR>

以下是按下F8 的畫面

tagbar

網友推荐的taglist改良版 plugin,為什麼不換掉taglist呢?因為我喜歡source explorer。除了安裝Plugin外,我也順便設定按下F7可以切換,設定如下。

.vimrc 的Trinity設定
1
2
3
4
5
"====================================================================
" Tagbar Settings
"====================================================================
" Open and close the tagbar separately
nmap <F7> :TagbarToggle<CR>

以下是按下F7 的畫面,可以注意右邊視窗會更進一步地顯示資料結構的成員名稱

Markdown 語法支援

vim-pandoc-syntax

單純就是讓vim可以顯示Markdown syntax highlight,範例如下圖:

Python開發相關

準備工作

主要是語法檢查套件相關安裝,指令如下

.vimrc 的Trinity設定
1
sudo apt install -y flake8 python-rope pylint

python-mode

之前有介紹過,偷懶跳過。也許Python用到一陣子可以上手後可以再分享心得。

syntastic

泛用形語法檢查工具,請參考Syntax checking hacks for vim說明。 目前是我靠他幫忙檢查寫的程式是否符合PEP8規範,要注意的是Ubuntu 16.04中vim 套件預設只支援Python 3,要使用vim 編寫Python 2的朋友請自行估狗。我之前是自行編譯vim解決的。

語法檢查範例如下圖

python_match

讓Python 也可以使用vim中切換配對的快捷鍵%

python

提供下列快捷鍵,節錄自Plugin註解:

  • ]t – Jump to beginning of block
  • ]e – Jump to end of block
  • ]v – Select (Visual Line Mode) block
  • ]< – Shift block to left
  • ]> – Shift block to right
  • ]# – Comment selection
  • ]u – Uncomment selection
  • ]c – Select current/previous class
  • ]d – Select current/previous function
  • ]<up> – Jump to previous line with the same/lower indentation
  • ]<down> – Jump to next line with the same/lower indentation

indentpython

確保你的程式碼符合PEP8的indent規範

和Plugin 無關的設定

以下都加在.vimrc中,建議加到Vundle設定結束後以確保可能會用到的Plugin已經啟動

編輯器和顯示特殊字元相關設定

  • 設定gvim 的配色,請自行找Color scheme
    • colorscheme koehler
  • 設定gvim 的字型和大小
    • set guifont=Inconsolata\ for\ Powerline\ 32
  • 將找到的字串設成高亮度
    • set hlsearch
  • 游標在的該行背景高亮度
    • set cursorline
  • 顯示行號
    • set nu
  • 第八十字元地方顯示高亮度區塊(這是連續兩個描述)
    • set colorcolumn=80
    • highlight ColorColumn guibg=#202020
  • 顯示tab (這是連續兩個描述)
    • set listchars=tab:»\
      • 注意\後面有一個空白
    • set list

效果如下圖

Indent相關設定

  • Tab相關設定
    • set ts=4
      • tab space 為4個字元
    • set expandtab
      • 不使用tab,用空白字元代替
    • set shiftwidth=4
      • Auto indent的移動字元數量
  • visual 模式下一次移動一個indent
    • vnoremap < <gv
      • 往左移動一個indent
    • vnoremap > >gv
      • 往右移動一個indent

其他

  • set clipboard+=unnamed
    • PRIMARY selection的register "*包含vim的unnamed register。白話講就是其他的APP如gedit中滑鼠選字後可以用"*p貼到vim,同樣的"*y6y的結果可以貼在其他的APP如gedit上。這部份有vim register副本,建議到參考資料的副本區一讀。再次感謝Scott大大。

參考資料

懶人包

  • 安裝相關軟體,Vundle和airline字型
1
2
3
4
sudo apt-get install -y exuberant-ctags cscope vim-gtk git flake8 python-rope pylint
git clone https://github.com/VundleVim/Vundle.vim.git ~/.vim/bundle/Vundle.vim
git clone https://github.com/powerline/fonts
cd fonts && ./install.sh
  • 剪貼下面的文字並存放到 ~/.vimrc
.vimrc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
"====================================================================
" Start vundle
"====================================================================
set nocompatible              " be iMproved, required
filetype off                  " required

" set the runtime path to include Vundle and initialize
set rtp+=~/.vim/bundle/Vundle.vim
call vundle#begin()

" alternatively, pass a path where Vundle should install plugins
"call vundle#begin('~/some/path/here')

" let Vundle manage Vundle, required
Plugin 'VundleVim/Vundle.vim'

"===============================================================
" Write your plugins here
"===============================================================
" Layouts
Plugin 'Yggdroot/indentLine'
Plugin 'ntpeters/vim-better-whitespace'

" Markdown
Plugin 'vim-pandoc/vim-pandoc-syntax'

" Python related
Plugin 'python-mode/python-mode'
Plugin 'vim-scripts/indentpython.vim'
Plugin 'vim-syntastic/syntastic'
Plugin 'vim-scripts/python_match.vim'
Plugin 'vim-scripts/python.vim'

" Misc tools
Plugin 'kien/ctrlp.vim'
Plugin 'vim-airline/vim-airline'
Plugin 'tpope/vim-fugitive'
Plugin 'Valloric/YouCompleteMe'
Plugin 'chazy/cscope_maps'
Plugin 'vim-scripts/taglist.vim'
Plugin 'scrooloose/nerdtree'
Plugin 'wesleyche/SrcExpl'
Plugin 'wesleyche/Trinity'
Plugin 'majutsushi/tagbar'

"====================================================================
" Run vundle
"====================================================================
" All of your Plugins must be added before the following line
call vundle#end()            " required
filetype plugin indent on    " required
" To ignore plugin indent changes, instead use:
"filetype plugin on
"
" Brief help
" :PluginList       - lists configured plugins
" :PluginInstall    - installs plugins; append `!` to update or just :PluginUpdate
" :PluginSearch foo - searches for foo; append `!` to refresh local cache
" :PluginClean      - confirms removal of unused plugins; append `!` to auto-approve removal
"
" see :h vundle for more details or wiki for FAQ
" Put your non-Plugin stuff after this line

"====================================================================
" Tagbar Settings
"====================================================================
" Open and close the tagbar separately
nmap <F7> :TagbarToggle<CR>

"====================================================================
" Trinity Settings
"====================================================================
" Open and close all the three plugins on the same time
nmap <F8>  :TrinityToggleAll<CR>

" Open and close the Source Explorer separately
nmap <F9>  :TrinityToggleSourceExplorer<CR>

" Open and close the Taglist separately
nmap <F10> :TrinityToggleTagList<CR>

" Open and close the NERD Tree separately
nmap <F11> :TrinityToggleNERDTree<CR>

"====================================================================
" Airline settings
"====================================================================
let g:airline_powerline_fonts = 1
set laststatus=2

"====================================================================
" syntastic settings
"====================================================================
set statusline+=%#warningmsg#
set statusline+=%{SyntasticStatuslineFlag()}
set statusline+=%*

let g:syntastic_always_populate_loc_list = 1
let g:syntastic_auto_loc_list = 1
let g:syntastic_check_on_open = 1
let g:syntastic_check_on_wq = 0
autocmd VimEnter * SyntasticToggleMode " disable syntastic by default


"====================================================================
" pymode settings
"====================================================================
let g:pymode_lint = 0    " Prefer to use syntastic to check lint
let g:pymode_folding = 0 " Unfold all

"====================================================================
" Editor and display Settings
"====================================================================
colorscheme koehler         " Color for gvim

set hlsearch                " Highlight search
set guifont=Inconsolata\ for\ Powerline\ 32 " Font
set cursorline              " Hight background at current cursor line
set nu                      " Display line numbers

" Set background color at colum 80
set colorcolumn=80
highlight ColorColumn guibg=#202020

" Show tabs
set listchars=tab:\|.
set list

" Ensure syntax is on
syntax on

"====================================================================
" Indent Settings
"====================================================================
" Tabs
set ts=4
set expandtab
set shiftwidth=4

" visual indent shift
vnoremap < <gv
vnoremap > >gv

"====================================================================
" MISC Settings
"====================================================================
" Shared unamed regitered with primary selection
set clipboard+=unnamed

" uft-8 encoding: https://stackoverflow.com/questions/16507777/set-encoding-and-fileencoding-to-utf-8-in-vim
set encoding=utf-8
set fileencoding=utf-8

"====================================================================
" Python Settings
"====================================================================
au BufNewFile,BufRead *.py
    \ set tabstop=4 |
    \ set softtabstop=4 |
    \ set shiftwidth=4 |
    \ set textwidth=79 |
    \ set expandtab |
    \ set autoindent |
    \ set fileformat=unix
let python_highlight_all=1
  • gvim -> :PluginInstall 安裝Plugin重新開啟收工

其他

  • cscope產生database供vim使用
    • cscope -bqkR
      • k表示使用kernel mode,不把/usr/include之類的加入資料庫。Cross compile也不會使用host 的header file,所以請自行斟酌。其他參數請自己問男人。
  • ctags產生database供vim使用
    • ctags -R
  • 要在vim使用到ctags和cscope的話,請記得vim一定要開在database同一層目錄!

舉例來說,你在/tmp/linux-stable目錄下了上面兩個指令。要開啟檔案請在/tmp/linux-stable目錄中指定對應路徑。範例如下

1
user@host:/tmp/linux-stable$ gvim fs/select.c

Fix: Qemu X86_64下gdb Debug kernel出現Remote 'g’ Packet Reply Is Too Long:

| Comments

問題描述

本篇文章主要是解決使用gdb 設Qemu x86_64 模擬執行x86_64 buildroot kernel開機的中斷點時遇到下面的錯誤訊息

Remote 'g’ packet reply is too long:

詳細訊息如下

1
2
3
4
5
6
0x0000000000000000 in irq_stack_union ()
(gdb) b x86_64_start_kernel
Breakpoint 1 at 0xffffffff8188429b: file arch/x86/kernel/head64.c, line 134.
(gdb) c
Continuing.
Remote 'g' packet reply is too long: 9b428881ffffffff0000000000000000010100c000000000ffffffff00000000804001000000000080400100000000000000000000000000f03f8081ffffffff00a080010000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000009b428881ffffffff4600000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007f0300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000801f0000

目錄

測試環境

  • Buildroot
    • Commit: bfc90a5621c680000f8b19e8afea944da5c2a469
  • Target kernel 版本
1
2
# uname -a
Linux buildroot 4.9.6 #2 SMP Sun Jun 4 18:46:27 CST 2017 x86_64 GNU/Linux
  • Qemu 執行指令
1
2
3
4
$ qemu-system-x86_64 -M pc -kernel output/images/bzImage                        \
                     -drive file=output/images/rootfs.ext2,if=virtio,format=raw \
                     -append "root=/dev/vda console=ttyS0"                      \
                     -net nic,model=virtio -net user -nographic -S -s

基本上是從這邊衍生出來的,挑幾個重點

  • -append "root=/dev/vda console=ttyS0"
    • 指定serial port console,如此一來就可以在terminal 直接顯示Qemu 的執行文字,不過你需要修改rootfs 的/etc/inittab才能在terminal login
  • -S -s
    • 開機的時候就停下來,並開啟port 1234讓gdb從遠端連入除錯
  • -nographic
    • 懶得跳一個視窗,直接terminal當console使用

buildroot 事先準備

  1. 下載buildroot
    • git clone https://git.buildroot.net/buildroot
  2. 設定預設config
    • make qemu_x86_64_defconfig
  3. 手動設定buildroot config如gcc版本,客製化rootfs套件等
    • make menuconfig
  4. 設定Linux kernel 選項,主要是打開debug symbol
    • make linux-menuconfig
  5. 編譯rootfs及kernel
    • make
  6. 設定可以從console 登入

gdb 錯誤訊息解法

OSDev: QEMU and GDB in long mode可以看到可以使用下面指令頂著先(workaround)

  • disconnect
  • set arch i386:x86-64
  • target remote 127.0.0.1:1234

然而作為組裝工,信奉偷懶就是美德,每次要打這麼多指令實在很麻煩。因此我將這些麻煩的方式使用下面的指令自動化

1
2
3
4
5
6
gdb ./vmlinux  -ex "target remote localhost:1234"       \
               -ex "break x86_64_start_kernel"          \
               -ex "continue"                           \
               -ex "disconnect"                         \
               -ex "set architecture i386:x86-64:intel" \
               -ex "target remote localhost:1234"

執行後畫面輸出部份節錄如下

1
2
3
4
5
6
7
8
9
10
11
12
13
Reading symbols from ./vmlinux...done.
Remote debugging using localhost:1234
0x0000000000000000 in irq_stack_union ()
Breakpoint 1 at 0xffffffff8188429b: file arch/x86/kernel/head64.c, line 134.
Continuing.
Remote 'g' packet reply is too long: 9b428881ffffffff0000000000000000010100c000000000ffffffff00000000804001000000000080400100000000000000000000000000f03f8081ffffffff00a080010000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000009b428881ffffffff4600000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007f0300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000801f0000
Ending remote debugging.
The target architecture is assumed to be i386:x86-64:intel
Remote debugging using localhost:1234
x86_64_start_kernel (real_mode_data=0x14080 <cpu_tss+6848> <error: Cannot access memory at address 0x14080>) at arch/x86/kernel/head64.c:134
134   {
(gdb) n
151       cr4_init_shadow();

由於gdb command file 遇到錯誤就會停下來,所以把上面的指令放到一個檔案中,執行gdb時將會停在continue這邊,目前懶的找解法了。有興趣的朋友可以自行研究。

懶人包

  • Buildroot
1
2
3
4
5
6
git clone https://git.buildroot.net/buildroot
cd buildroot
make qemu_x86_64_defconfig
make menuconfig
make linux-menuconfig
make
  • 啟動Qemu 假設在buildroot top directory下
1
2
3
4
qemu-system-x86_64 -M pc -kernel output/images/bzImage                          \
                     -drive file=output/images/rootfs.ext2,if=virtio,format=raw \
                     -append "root=/dev/vda console=ttyS0"                      \
                     -net nic,model=virtio -net user -nographic -S -s
  • gdb 假設在buildroot top directory下
1
2
3
4
5
6
7
cd output/build/linux-4.9.6
gdb ./vmlinux  -ex "target remote localhost:1234"       \
               -ex "break x86_64_start_kernel"          \
               -ex "continue"                           \
               -ex "disconnect"                         \
               -ex "set architecture i386:x86-64:intel" \
               -ex "target remote localhost:1234"

讓qemu-system-x86_64 在console 可以登入

/etc/inittab加入下面這行ttyS0::respawn:/sbin/getty -L ttyS0 0 vt100 # GENERIC_SERIAL

/etc/inittab
1
2
3
 # Put a getty on the serial port
 tty1::respawn:/sbin/getty -L  tty1 0 vt100 # GENERIC_SERIAL
+ttyS0::respawn:/sbin/getty -L  ttyS0 0 vt100 # GENERIC_SERIAL

參考資料

Bash下自動完成gvim –remote-tab

| Comments

在gvim需要開tab要打--remote-tab,打久就開始厭煩想偷懶。後來整理網路上的bash_completion相關資料拼湊一個堪用的版本分享一下。因為我只是想偷懶,所以完全沒有去了解bash_completion的細節。嘛,反正可以組裝需要的功能就好。

目錄

測試環境

1
2
3
4
5
6
$ lsb_release  -a
No LSB modules are available.
Distributor ID:   Ubuntu
Description:  Ubuntu 16.04.2 LTS
Release:  16.04
Codename: xenial

安裝方式

  1. 下面文字貼到編輯器,存成/etc/bash_completion.d/gvim
  2. 使用下面指令新增gvim completion
新增gvim completion指令
1
$ . /etc/bash_completion.d/gvim

使用方式

  • 一般使用,直接gvim tab 顯示檔案或目錄
新增gvim completion指令
1
2
3
4
$ gvim `tab`
changelog.Debian.gz  copyright            README.emacs         RelNotes/
changelog.gz         NEWS.Debian.gz       README.md
contrib/             README.Debian        README.source
  • 要使用tab時,下gvim -tab 就會自動填入--remote-tab,接下來再按tab即可選擇檔案或目錄
新增gvim completion指令
1
2
3
4
$ gvim --remote-tab
changelog.Debian.gz  copyright            README.emacs         RelNotes/
changelog.gz         NEWS.Debian.gz       README.md
contrib/             README.Debian        README.source

gvim bash_completion script

gvim
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
_gvim()
{
    local cur prev
    COMPREPLY=()
    cur="${COMP_WORDS[COMP_CWORD]}"
    prev="${COMP_WORDS[COMP_CWORD-1]}"
    opts="--remote-tab"

    if [[ ${cur} == -* ]] ; then
        COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
        return 0
    fi

    compopt -o default; COMPREPLY=()
    return 0
}
complete -F _gvim gvim

參考資料

筆記 - GNU Coding Style

| Comments

這是沒事亂看的,主要找和C 語言相關的描述,就不要在意文章組織和可讀性了。

閱讀版本:July 25, 2016

CH 3

  • 要保證C程式碼可移植性,compile flag可以使用--ansi, --posix, --compatible
  • 可以使用下面的方式取代原本的巨集條件編譯,現在的gcc已經可以產生一樣的結果了。
原本程式碼
1
2
3
4
5
#ifdef HAS_FOO
...
#else
...
#endif
建議使用方式
1
2
3
4
if (HAS_FOO)
...
else
...

Ch 4

  • --pedantic:產生所有ISO C和ISO C++規範的警告訊息,並將所有C的extension退貨
  • 冷門:POSIX 2.0規範du/df的unit是512 bytes

4.2 Writing Robust Programs

  • 使用動態配置資料方式避免資料長度限制,包含檔案名稱長度、一行長度等
  • 讀檔案時不得丟棄NUL字元以及不可列印字元,並需相容多位元編碼方式如UTF-8
  • 除非要刻意忽略錯誤,使用system call必定檢查回傳值,並使用perrorstrerror等方式列印出錯誤訊息
  • 使用mallicrelloc一定要檢查回傳值是否為NULL。當使用relloc要求分配比原本空間更小時更需要注意,因為實作關係可能最後分配的空間block可能和原本的不同
  • 使用free後就不要留戀還在裏面的資料了吧
  • 一旦malloc出現錯誤時,非互動程式請將他視為嚴重錯誤。互動程式就儘早自殺(abort())吧
  • 使用getopt_long處理命令列參數
  • 避免操作低階UNIX介面,以減少相容性問題
  • 在程式中檢查到不可能發生的狀況,直接給他死。既然不可能發生了,顯然那邊出大問題,儘早發現儘早處理。因為就是不可能發生,而死掉一定從這邊找起,寫程式時就在這邊提供更多註解和資訊吧
  • 使用全域變數或static 變數儘量給初值。(不確定是否有看懂,出處
  • 不要從回傳值傳回發生錯誤的次數,因為只有256個狀態而已,很容易overflow的。
  • 使用存放暫存檔到$TMP_DIR環境變數而不是閉著眼睛寫到/tmp下面
  • 同樣的,暫存檔請將權限設為0600確保資訊不會洩漏

CH 4.3 Library Behavior

  • 儘量讓函數reentrant
  • Name covention 很重要,因為library是給別人用,粗心大意就出現name conflict 的問題,以下是GNU的建議
    • external 函數和變數需加prefix,prefix為兩個字元以上(後面英文太爛看不懂)
    • external 但是不想讓使用者看到(或是文件不會提到的)的函數以_開頭加上prefix用來辨識這是內部函數
    • 內部static 就請自便

CH 4.4 Formatting Error Messages

  • 錯誤訊息格式,我只挑一個,有興趣看全文請參考這邊
    • sourcefile:lineno: message

CH 4.5 Standards for Interfaces Generally

很奇怪的,單字句子都看懂,就是不懂他要表達啥。只能猜測說不要生出一堆很功能類似的執行檔,而是用最少執行檔加上參數取代。以及執行檔儘量device independent

4.12 File Usage

檔案有可能會存放在readonly FS,如果要寫東西寫到必定是可寫的目錄如/var或是/tmp目錄中

Writing C

  • 單行最多79個字元

註解

  • 檔案開頭請加註解說明該檔案的用途
  • 用英文寫註解
  • 每個函數都要說明該函數的用法,參數,回傳值等資訊
  • #endif 後加上對應的#ifdef#ifndef 說明,範例如下:
1
2
3
4
5
6
7
8
9
#ifdef ASDF
...
#else  /* ASDF */
...
#endif /* not ASDF */

#ifnef QWER
...
#endif /* not QWER */

Clean Use of C Constructs

  • 所有的宣告都要明確,例如不要因為預設回傳值是int就省略要回傳int的函數對應宣告
  • 視情況自行決定是否要使用嚴格的語法檢查如-Wall,編譯器是我們的奴隸,你自己清楚要做什麼就好。不要為了避免嚴格語法檢查硬上一些奇怪的語法導致可讀性變差
  • extern 要嘛全部放在C檔案的同一塊地方,要嘛集中到header file,不要東一撮西一撮。更嚴禁在函數內使用
  • 不要在函數中重複使用一些無意義名稱的變數如tmp之類的,個別變數就給予個別有意義的變數名稱,不要偷懶。也可以在需要的最小scope宣告變數增加可讀性。
  • 變數要注意是否和global 變數或是更大scope名稱相同,您可以開啟-Wshadow協助偵測這類的錯誤
  • 多個變數就個別宣告型態,一方面增加可註解的空間,一方面可讀性也較佳。範例如下
1
2
3
4
5
6
/* Bad */
int i_am_bad, i_am_super_bad;

/* Good */
int i_am_good   = 0;
int i_am_better = 0;
  • 不要在判斷式加入assign,這樣很容易出錯,範例如下
1
2
3
4
5
6
7
/* Bad */
if ((my_id = get_id(my_record)) != NONE) {
    /* Prcess data */
}
else {
    return NO_DATA;
}
1
2
3
4
5
6
/* Good */
my_id = get_id(my_record);
if (my_id == NONE) {
    return NO_DATA;
}
/* Prcess data */

Naming Variables, Functions, and Files

  • Global name 要有意義,不要亂取。
    • ps: 個人會加個g prefix提醒一下
  • 縮寫如src等可以使用。但是請確定讀者可以知道該縮寫或是該縮寫可能造成讀者模糊混淆
  • 使用常數時儘量用enum取代巨集

Portability between CPUs

完全看不懂,抱歉

剩下的是平台相容性和國際化字元等,沒興趣所以跳過

參考資料

Linux Kernel Pratice 0.5: 使用gdb 加 Qemu Trace Linux Kernel Runtime 行為

| Comments

目錄

測試環境

Host

  • Host OS
1
2
3
4
5
6
$ lsb_release -a
No LSB modules are available.
Distributor ID:   Ubuntu
Description:  Ubuntu 14.04.5 LTS
Release:  14.04
Codename: trusty
  • Qemu
1
2
$ qemu-system-arm --version
QEMU emulator version 2.0.0 (Debian 2.0.0+dfsg-2ubuntu1.27), Copyright (c) 2003-2008 Fabrice Bellard
  • gdb
1
2
3
4
5
$ arm-none-eabi-gdb --version
GNU gdb (GNU Tools for ARM Embedded Processors) 7.10.1.20160923-cvs
Copyright (C) 2015 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl
...
  • buildroot 版本
    • commit hash: 14b24726a81b719b35fee70c8ba8be2d682a7313

Target

  • Linux kernerl 版本
    • 4.4.2
  • 模擬平台
    • Vexpress

Linux kernel環境設定

我目前只打開加入debug資訊的選項。接下來重編,編譯的方式請參考這邊

  • Kernel hacking -> Compile-time checks and compiler options ->
    • Compile the kernel with debug info

測試

依照下面兩個步驟執行

  1. 執行qemu,並且加入支援gdb以及開始馬上freeze CPU的參數
  2. 執行gdb,載入symbol並聯到qemu除錯

Qemu

基本上就是原本的指令加入兩個選項

  • -S
    • qemu一開始立即Freeze CPU
  • -s
    • -gdb tcp::1234的縮寫,也就是說gdb可以透過port 1234和連到Qemu除錯

假設你在buildroot最上層,就可以使用下面指令執行qemu 並使用gdb 除錯

1
2
3
4
5
6
qemu-system-arm -M vexpress-a9 -smp 1 -m 256 \
                -kernel /tmp/kernel/linux-stable/arch/arm/boot/zImage  \
                -dtb /tmp/kernel/linux-stable/vexpress-v2p-ca9.dtb     \
                -drive file=output/images/rootfs.ext2,if=sd,format=raw \
                -append "console=ttyAMA0,115200 root=/dev/mmcblk0"     \
                -serial stdio -net nic,model=lan9118 -net user -s -S

gdb

這邊有點瑣碎,先講一下步驟

  1. 載入Linux kernel symbol
    • 假設你在kernel最上層目錄有三種方式載入
      • 直接arm-none-eabi-gdb ./vmlinux
      • arm-none-eabi-gdb -ex "file ./vmlinux"
      • 進入gdb後打file ./vmlinux指令
  2. 連上qemu
    • 一樣兩種方式
      • arm-none-eabi-gdb -ex "target remote :1234"
      • 進入gdb後打target remote :1234指令
  3. 設定breakpoint等你要觀察的資訊
    • b printk
  4. 告訴qemu開始執行
    • continue

1和2可以一起使用如下

懶人包
1
arm-none-eabi-gdb -ex "file ./vmlinux"  -ex "target remote :1234"

現在看一下操作範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ arm-none-eabi-gdb -ex "file ./vmlinux"  -ex "target remote :1234"
GNU gdb (GNU Tools for ARM Embedded Processors) 7.10.1.20160923-cvs
...
Reading symbols from ./vmlinux...done.
Remote debugging using :1234
0x60000000 in ?? ()
(gdb) b printk
Breakpoint 1 at 0x800a2260: file kernel/printk/printk.c, line 1900.
(gdb) c
Continuing.

Breakpoint 1, printk (fmt=0x0 <__vectors_start>) at kernel/printk/printk.c:1900
1900  {
(gdb) bt
#0  printk (fmt=0x0 <__vectors_start>) at kernel/printk/printk.c:1900
#1  0x806178e0 in start_kernel () at init/main.c:508
#2  0x6000807c in ?? ()
Backtrace stopped: previous frame identical to this frame (corrupt stack?)

另外如果有興趣使用Linux kernel提供的指令,在kernel config設定打開gdb script後,可以使用下面的方式在啟動gdb時載入,只要把下面的描述加到你的~/.gdbinit即可

1
add-auto-load-safe-path /tmp/kernel/scripts/gdb/vmlinux-gdb.py

那麼你就可以使用Linux kernel提供的gdb script,詳細的設定和指令說明在這邊

參考資料

補充

當初犯了蠢事載入不正確的kernel image導致一堆不必要的除錯。不過多學到一個gdb Python script除錯指令,當Python script發生exception時可以用下面的指令印出Python錯誤call stack

  • gdb內:set python print-stack full
  • 啟動gdb時加入參數: -ex "set python print-stack full"

Linux Kernel Pratice 0: Buildroot (2/2) - 自行編譯kernel

| Comments

前情提要

上一篇提到,設定實習環境的目標有:

  1. 可以使用ARM 平台。一方面追求流行,一方面我不想再開x86這個副本
  2. 可以方便地建立ARM平台的Linux Rootfs和kernel版本
  3. 可以方便地更改指定要編譯的Kernel版本
  4. 透過Qemu ,使用2的Rootfs和kernel開機
  5. 透過Qemu和搭配的工具可以分析Linux kernel的run time 行為

今天就是來解決3 的項目。更細分的話,這次目標是

  1. 使用官方Linux kernel 編譯Vexpress 平台kernel及產生Buildroot支援的開發版device tree
  2. 編譯出來的kernel binary可以在Qemu上順利載入
  3. 編譯出來的kernel binary可以順利的載入buildroot產生的rootfs,以及網路介面和相關設備

目錄

測試環境

做組裝的最重要的原則之一就是要能夠reproduce,所以交代測試環境是一定要的

1
2
3
4
5
6
7
8
9
$ lsb_release -a
No LSB modules are available.
Distributor ID:   Ubuntu
Description:  Ubuntu 14.04.5 LTS
Release:  14.04
Codename: trusty

$ git --version
git version 2.10.0
  • buildroot 版本
    • commit hash: 14b24726a81b719b35fee70c8ba8be2d682a7313

下載Linux Kernel Source

沒啥好講的,就剪貼指令吧

1
git clone git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git

設定和編譯

東西下載完不是就閉著眼睛開幹,因為我們在開始編譯前需要

  1. 切換到你想要研究的版本
  2. 如果不是x86,你需要指定平台
  3. 細項Kernel config設定

那麼就來見招拆招吧

切換版本

非常簡單,先git tag,切過去就好。我要切到4.4.2就是

1
2
3
git tag                 # 找你要的版本
git checkout v4.4.2     # 切到tag
git checkout -b v4.4.2  # 理論上會這邊改東改西,就先開branch吧

設定ARM Vexpress預設config

先講結論

1
make ARCH=arm vexpress_defconfig

開始沒人要看的解釋吧。基本上也亂看找出來的,簡單講一下當初的「脈絡」

  1. 我知道我們平台是vexpress,所以就find | grep vexpress。從一堆檔案中我看到有趣的檔案./arch/arm/configs/vexpress_defconfig
  2. 接下來就是要找make 的時候怎麼和這個檔案勾起來。網路上找一下會發現一個變數ARCH,剩下就試看看make ARCH=arm vexpress_defconfig,能不能動,可以動所以打完收工。

然後你就知道

  1. Linux kernel source中有些平台會提供default config
  2. 透過ARCH可以讓make時自動參考這些檔案產生config

更改Kernel Config讓Qemu使用

建議不要把buildroot compile cache打開。我花了很多時間在kernel 編譯後Qemu還是沒有使用編譯後的kernel的問題,最後發現關閉buildroot compile cache問題就消失了。

如果閉著眼睛開始編譯,你會很高興地發現可以開機了,但是接下來就會很失望的發現出現mount完rootfs找不到/dev/ttyAMA0,以至於沒辦法進入login畫面。這是因為雖然serial driver偵測到設備,但是/dev下面沒有相對的device node。解法就是確認下面kernel option有開啟。想要知道真正的原因手冊這邊有提到,請參考Dynamic using devtmpfs only段落。

  • Device Drivers -> Generic Driver Options ->
    • Maintain a devtmpfs filesystem to mount at /dev
    • Automount devtmpfs at /dev, after the kernel mounted the rootfs

建議順便巡一下其他kernel選項,用不到的可以關一下。比如說一堆有的沒的網卡,音效支援之類的。

編譯

Buildroot

  1. make menuconfig
    • Toolchain -> Custom kernel headers series
      • 改成你現在Linux 版本
  2. make

Linux kernel

指令如下

1
make CROSS_COMPILE=/tmp/buildroot/output/host/usr/bin/arm-buildroot-linux-gnueabihf- ARCH=arm V=1 bzImage

這其實就是make bzImage的囉唆版,多了

  • ARCH=arm
    • 指定ARM平台
  • CROSS_COMPILE=..
    • Cross compile prefix,既然我們使用buildroot內建toolchain,就用他們來編譯kernel
  • V=1
    • 身為組裝工,沒看到編譯指令訊息跳出來就會沒安全感

產生Device tree binary

由於Kernel的演進,可以存放平台硬體相關設定讓kernel啟動時存取。此方式稱為device tree,詳細資訊可以參考這份簡介(pdf投影片)

以論述文來說,儘量先說結論再解釋。因此懶人包如下,假設在kernel top 目錄中:

1
2
sudo apt-get install device-tree-compiler
dtc -O dtb -o vexpress-v2p-ca9.dtb arch/arm/boot/dts/vexpress-v2p-ca9.dts

沒人要看的說明如下,懶得看推論的就剪貼上面就好 對於組裝工來說,我關心的是

  1. Kernel 軟體包中是否有存在已經有的device tree?
  2. 有的話,我要選那一個?
  3. 怎麼產生出最後成果?
  4. Qemu怎麼使用device tree?

要回上面的問題,最簡單的方式就是回顧buildroot中啟動qemu Vepress的命令參數,你就會發現有個東西似乎和我們關心的device tree有關聯

  • -dtb output/images/vexpress-v2p-ca9.dtb

這邊顯示了幾個資訊

  • 有一個檔案叫dtb
  • 檔名的v2p-ca9有可能和平台有關係

那麼我們在Kernel中找一下檔名中有vexpress-v2p-ca9的檔案

1
2
user@host:/tmp/kernel/linux-stable$ find | grep vexpress-v2p-ca9
./arch/arm/boot/dts/vexpress-v2p-ca9.dts

這邊一樣透露了這是一個和dtb很類似的檔案,那麼我們進一步做一些確認

1
2
3
4
5
6
7
8
9
$ # dtb 是binary檔案
$ file output/images/vexpress-v2p-ca9.dtb
output/images/vexpress-v2p-ca9.dtb: data

$ # dts是文字檔
$ file ./arch/arm/boot/dts/vexpress-v2p-ca9.dts
./arch/arm/boot/dts/vexpress-v2p-ca9.dts: ASCII text

$ # cat 後會發現是一種描述檔

剩下就是估狗大法,發現需要編譯器才能把dts轉換成dtb檔案。 所以就用上面的方式產稱dtb檔囉。

測試

剩下就剪貼了

我在buildroot top目錄執行的,你要嘛就切到buildroot目錄下,要嘛就指定-drive file到你自己rootfs的路徑

1
qemu-system-arm -M vexpress-a9 -smp 1 -m 256 -kernel /tmp/kernel/linux-stable/arch/arm/boot/zImage -dtb /tmp/kernel/linux-stable/vexpress-v2p-ca9.dtb  -drive file=output/images/rootfs.ext2,if=sd,format=raw -append "console=ttyAMA0,115200 root=/dev/mmcblk0" -serial stdio -net nic,model=lan9118 -net user

提出來兩個參數表示這是我編譯出來的kernel而不是buildroot的

  • -kernel /tmp/kernel/linux-stable/arch/arm/boot/zImage
  • -dtb /tmp/kernel/linux-stable/vexpress-v2p-ca9.dtb

開機畫面節錄如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Booting Linux on physical CPU 0x0
Initializing cgroup subsys cpuset
Linux version 4.4.2 (user@host) (gcc version 4.8.5 (Buildroot 2016.11-git-00439-g14b2472) ) #10 SMP Sat Oct 29 15:25:36 CST 2016
CPU: ARMv7 Processor [410fc090] revision 0 (ARMv7), cr=10c5387d
CPU: PIPT / VIPT nonaliasing data cache, VIPT aliasing instruction cache
Machine model: V2P-CA9
Memory policy: Data cache writeback
CPU: All CPU(s) started in SVC mode.
PERCPU: Embedded 12 pages/cpu @8fdbd000 s18188 r8192 d22772 u49152
Built 1 zonelists in Zone order, mobility grouping on.  Total pages: 65024
Kernel command line: console=ttyAMA0,115200 root=/dev/mmcblk0
log_buf_len individual max cpu contribution: 4096 bytes
log_buf_len total cpu_extra contributions: 12288 bytes
log_buf_len min size: 16384 bytes
log_buf_len: 32768 bytes
early log buf free: 14956(91%)
PID hash table entries: 1024 (order: 0, 4096 bytes)
Dentry cache hash table entries: 32768 (order: 5, 131072 bytes)
Inode-cache hash table entries: 16384 (order: 4, 65536 bytes)
Memory: 252732K/262144K available (4816K kernel code, 155K rwdata, 1384K rodata, 284K init, 152K bss, 9412K reserved, 0K cma-reserved)
Virtual kernel memory layout:
    vector  : 0xffff0000 - 0xffff1000   (   4 kB)
    fixmap  : 0xffc00000 - 0xfff00000   (3072 kB)
    vmalloc : 0x90800000 - 0xff800000   (1776 MB)
    lowmem  : 0x80000000 - 0x90000000   ( 256 MB)
    modules : 0x7f000000 - 0x80000000   (  16 MB)
....
buildroot login:

參考資料

附錄

使用Buildroot 內建套件指定編譯Linux kernel 4.2.2

當初會去做這個主要是因為開始編譯獨立的Linux kernel前要先驗證buildroot自己編的Linux 4.4.2是否可以用qemu開機。另外的好處的就是buildroot編譯出來的kernel config (在output/build/linux-4.4.2/.config) 可以和你自己的kernel config比對。這邊只要在buildroot中make menuconfig中和kernel有關設定指定4.4.2即可,比Versatile簡單太多了故省略,如果有人遇到問題我再補上這邊說明。

Linux Kernel Pratice 0: Buildroot (1/2)

| Comments

理論上不應該要邊移動邊開火,延長戰線。不過計劃趕不上變化,既來之則安之。

最近因為特別因素開始學習Linux kernel,看能不能Linux kernel和STM32兩邊都不要漏掉。不管怎樣,學習和實習絕對分不開,所以還是從環境架設開始吧。這次的實習環境架設的目標是:

  1. 可以使用ARM 平台。一方面追求流行,一方面我不想再開x86這個副本
  2. 可以方便地建立ARM平台的Linux Rootfs和kernel版本
  3. 可以方便地更改指定要編譯的Kernel版本
  4. 透過Qemu ,使用2的Rootfs和kernel開機
  5. 透過Qemu和搭配的工具可以分析Linux kernel的run time 行為

今天只有辦到1, 2和4而已,剩下的請參考之後的文章。

目錄

測試環境

因為我已經裝過開發相關的套件,因此如果您是新手可能要自行摸索也許有需要另外安裝的套件如git。嘛,練習解讀錯誤訊息也是一種學習。

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 14.04.5 LTS
Release:    14.04
Codename:   trusty

安裝Buildroot

主要分成下面三個步驟

下載Buildroot

直接看例子,剪下貼上就好

1
2
3
mkdir buildroot
cd buildroot
git clone git://git.buildroot.net/buildroot

設定ARM 環境

網路上查到大部分都是從make menuconfig開始。不過我是很明確地要用Qemu跑ARM的系統。所以使用下面指令查詢。

1
make list-defconfigs

Scott 大大指出可以使用qemu_arm_vexpress ,原因是這個平台模擬的CPU是 Cortex-A9(ARMv7-A)的平台。之前我是用的模擬平台使用的CPU是ARM926EJ-S(ARMv5TE),它的instruction 架構和現在差距太多,所以就轉換到這邊了。

接下來就用make menuconfig做細項調整,因為是拿來做分析系統行為,所以調整的重點是增加系統的可觀察度、除錯工具、開發軟體套件等。

開啟或新增下面設定如下:

  • Build options
    • build packages with debugging symbol
    • gcc debug level
      • debug level 3
    • strip command for binaries on target
      • none
    • gcc optimization level
      • optimize for debugging
  • Toolchain
    • C library
      • glibc
    • glibc version
      • 2.24
    • GCC compiler Version
      • 4.8.x
    • Build cross gdb for the host
      • TUI support
      • Python support
      • Simulator support
  • Target packages
    • Debugging, profiling and benchmark
      • gdb
        • gdbserver
        • full debugger
        • TUI support
      • ltrace
      • strace
      • valgrind 和所有相關的東西
    • Development tools
      • binutils
      • git
      • gperf
      • libtool
      • make
      • pkgconf
      • subversion
      • tree

自行編譯 Kernel 部份下一篇會再說明。

編譯及輸出

編譯只要下make就會幫你下載和編譯開機需要的

  1. 套件和一些常用工具,並封裝到output/image/roofs.ext2
  2. Kernel(預設4.7),編譯成zImage,放在output/image/zImage

測試

接下來也不難,可以參考board/qemu/arm-vexpress/readme.txt 簡單來說就是執行下面指令,開機完使用root登入不用密碼,使用poweroff後再手動離開qemu。

1
qemu-system-arm -M vexpress-a9 -smp 1 -m 256 -kernel output/images/zImage -dtb output/images/vexpress-v2p-ca9.dtb -drive file=output/images/rootfs.ext2,if=sd,format=raw -append "console=ttyAMA0,115200 root=/dev/mmcblk0" -serial stdio -net nic,model=lan9118 -net user

執行畫面如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ qemu-system-arm -M vexpress-a9 -smp 1 -m 256 -kernel output/images/zImage -dtb output/images/vexpress-v2p-ca9.dtb -drive file=output/images/rootfs.ext2,if=sd,format=raw -append "console=ttyAMA0,115200 root=/dev/mmcblk0" -serial stdio -net nic,model=lan9118 -net user
Booting Linux on physical CPU 0x0
Initializing cgroup subsys cpuset
Linux version 4.4.2 (user@host) (gcc version 4.8.5 (Buildroot 2016.11-git-00439-g14b2472) ) #1 SMP Sat Oct 29 12:37:50 CST 2016
CPU: ARMv7 Processor [410fc090] revision 0 (ARMv7), cr=10c5387d
...
Initializing random number generator... done.
Starting network: smsc911x 4e000000.ethernet eth0: SMSC911x/921x identified at 0x912a0000, IRQ: 31
udhcpc: started, v1.25.0
udhcpc: sending discover
udhcpc: sending discover
udhcpc: sending select for 10.0.2.15
udhcpc: lease of 10.0.2.15 obtained, lease time 86400
deleting routers
adding dns 10.0.2.3
OK
Starting sshd: OK

Welcome to Buildroot
buildroot login:

參考資料

下次準備看的資料

(過期) Linux Kernel Pratice 0.5: 使用gdb 加 Qemu Trace Linux Kernel Runtime 行為

| Comments

感謝Scott 大大糾正,選錯平台,這篇使用了ARMv4指令集的測試平台。請大家忽略,正確的版本將會之後更新!

前言

還是感謝Scott大大和其他網友的幫忙。這次最重要的心得就是不要在睡眠不足的時候做實驗,浪費的時間拿去睡覺反而比較實在。

目錄

測試環境

Host

  • Host OS
1
2
3
4
5
6
$ lsb_release -a
No LSB modules are available.
Distributor ID:   Ubuntu
Description:  Ubuntu 14.04.5 LTS
Release:  14.04
Codename: trusty
  • Qemu
1
2
$ qemu-system-arm --version
QEMU emulator version 2.0.0 (Debian 2.0.0+dfsg-2ubuntu1.27), Copyright (c) 2003-2008 Fabrice Bellard
  • gdb
1
2
3
4
5
$ arm-none-eabi-gdb --version
GNU gdb (GNU Tools for ARM Embedded Processors) 7.10.1.20160923-cvs
Copyright (C) 2015 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl
...
  • buildroot 版本
    • commit hash: 14b24726a81b719b35fee70c8ba8be2d682a7313

Target

  • Linux kernerl 版本
    • 4.4.2
  • 模擬平台
    • Versatile

Linux kernel環境設定

我目前只打開加入debug資訊的選項。接下來重編,編譯的方式請參考這邊

  • Kernel hacking -> Compile-time checks and compiler options ->
    • Compile the kernel with debug info

測試

依照下面兩個步驟執行

  1. 執行qemu,並且加入支援gdb以及開始馬上freeze CPU的參數
  2. 執行gdb,載入symbol並聯到qemu除錯

Qemu

基本上就是原本的指令加入兩個選項

  • -S
    • qemu一開始立即Freeze CPU
  • -s
    • -gdb tcp::1234的縮寫,也就是說gdb可以透過port 1234和連到Qemu除錯

假設你在buildroot最上層,就可以使用下面指令執行qemu 並使用gdb 除錯

1
2
3
4
5
qemu-system-arm -M versatilepb \
                -kernel /tmp/kernel/arch/arm/boot/zImage \
                -drive file=output/images/rootfs.ext2,if=scsi,format=raw \
                -append "root=/dev/sda console=ttyAMA0,115200" \
                -serial stdio -net nic,model=rtl8139 -net user -s -S

gdb

這邊有點瑣碎,先講一下步驟

  1. 載入Linux kernel symbol
    • 假設你在kernel最上層目錄有三種方式載入
      • 直接arm-none-eabi-gdb ./vmlinux
      • arm-none-eabi-gdb -ex "file ./vmlinux"
      • 進入gdb後打file ./vmlinux指令
  2. 連上qemu
    • 一樣兩種方式
      • arm-none-eabi-gdb -ex "target remote :1234"
      • 進入gdb後打target remote :1234指令
  3. 設定breakpoint等你要觀察的資訊
    • b printk
  4. 告訴qemu開始執行
    • continue

1和2可以一起使用如下

懶人包
1
arm-none-eabi-gdb -ex "file ./vmlinux"  -ex "target remote :1234"

現在看一下操作範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ arm-none-eabi-gdb ./vmlinux  -ex "target remote :1234"
GNU gdb (GNU Tools for ARM Embedded Processors) 7.10.1.20160923-cvs
...
Reading symbols from ./vmlinux...done.
Remote debugging using :1234
__vectors_start () at arch/arm/kernel/entry-armv.S:1210
1210      W(b)    vector_rst
(gdb) b printk
Breakpoint 1 at 0xc0069df4: file kernel/printk/printk.c, line 1900.
(gdb) c
Continuing.

Breakpoint 1, printk (fmt=0x0 <__vectors_start>) at kernel/printk/printk.c:1900
1900  {
(gdb) bt
#0  printk (fmt=0x0 <__vectors_start>) at kernel/printk/printk.c:1900
#1  0xc039d8c4 in start_kernel () at init/main.c:508
#2  0x00008048 in ?? ()
Backtrace stopped: previous frame identical to this frame (corrupt stack?)

另外如果有興趣使用Linux kernel提供的指令,可以使用下面的方式在啟動gdb時載入 ,只要把下面的描述加到你的~/.gdbinit即可

1
add-auto-load-safe-path /tmp/kernel/scripts/gdb/vmlinux-gdb.py

那麼你就可以使用Linux kernel提供的gdb script,詳細的設定和指令說明在這邊

參考資料

補充

當初犯了蠢事載入不正確的kernel image導致一堆不必要的除錯。不過多學到一個gdb Python script除錯指令,當Python script發生exception時可以用下面的指令印出Python錯誤call stack

  • gdb內:set python print-stack full
  • 啟動gdb時加入參數: -ex "set python print-stack full"

使用gdb 靜態分析C 語言名稱和參數相同函數的原始檔位置

| Comments

前言

感謝Scott大大的資訊。

忘記那邊看過的名言:「手上有了新玩具,就會想要馬上拿來用。」

動機

組裝軟體的時候,有一件事很讓我困擾。那就是當整包SDK有兩個以上名稱和參數相同的函數的時候。當這種情況發生時,trace code就很麻煩,你必須要花時間釐清到底最後會使用到那一個函數。而這些config可能用下面兩種方式切換這些名稱和參數相同的函數:

  1. 巨集
    • #if
    • #ifdef
  2. Makefile
    • 在檔案中根據不同變數編譯不同的檔案

我以前會視情況用下面兩種方法找到該函數編譯實際使用的原始檔位置

  1. 下毒藥,只要相同名字的函數都塞入#error 可以辨別的字串。編譯時根據錯誤訊息判斷使用哪個函數
  2. gdb設斷點,runtime透過中斷函數的方式取得函數的檔案和行號

不過Scott大大的今天給的資訊讓我可以更省力的處理這個問題了。

如果只想要知道用法,看完下面指令就可以收工回家了。

1
gdb -ex 'file 你的執行檔或是shared library檔' -ex 'info line 要查的函數' -ex 'quit'

當然用gdb編譯時不要忘記加debug option。

目錄

測試環境

1
2
3
4
5
6
$ lsb_release -a
No LSB modules are available.
Distributor ID:   Ubuntu
Description:  Ubuntu 14.04.5 LTS
Release:  14.04
Codename: trusty

測試檔案

簡單來說,就是實作切換兩種方式同樣名稱和參數的函數:

  1. 巨集
  2. Makefile

所以我們會有

  1. 測試檔案進入點
  2. 測試檔一
    • 實作使用巨集OP1切換同樣名稱和參數
    • 和測試檔二完全一模一樣
  3. 測試檔二
    • 實作使用巨集OP1切換同樣名稱和參數
    • 和測試檔一完全一模一樣
  4. Makefile
    • 除了編譯以外,還提供兩個變數,由command line傳入
      • USE_FILE=1
        • 沒傳入時預設編譯測試檔二,當該參數傳入USE_FILE=1時會變成編譯測試檔一
      • EN_OP1=1
        • 當該參數傳入時才會開啟OP1巨集

test_same_func.c

  • 測試檔案進入點,呼叫func1()。func1()在compile time才被決定
test_same_func.c
1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
extern void func1(void);

int main()
{
    printf("Hello World\n");
    func1();

    return 0;
}

same_func1.c

  • 實作使用巨集OP1切換同樣名稱和參數
  • same_func2完全一模一樣
same_func1.c
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>

#if OP1
void func1(void)
{
    printf("%s: %s_OP1\n", __FILE__, __PRETTY_FUNCTION__);
}
#else
void func1(void)
{
    printf("%s: %s_NOT_OP1\n", __FILE__, __PRETTY_FUNCTION__);
}
#endif

same_func2.c

  • 實作使用巨集OP1切換同樣名稱和參數
  • same_func1完全一模一樣
same_func2.c
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>

#if OP1
void func1(void)
{
    printf("%s: %s_OP1\n", __FILE__, __PRETTY_FUNCTION__);
}
#else
void func1(void)
{
    printf("%s: %s_NOT_OP1\n", __FILE__, __PRETTY_FUNCTION__);
}
#endif

Makefile

提供兩個變數,由command line傳入

  • USE_FILE=1
    • 沒傳入時預設編譯測試檔二,當該參數傳入USE_FILE=1時會變成編譯測試檔一
  • EN_OP1=1
    • 當該參數傳入時才會開啟OP1巨集
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
CFLAGS+=-Wall -Werror -g3
TARGET=test_same_func
SRCS=test_same_func.c

ifeq ($(USE_FILE), 1)
    SRCS += same_func1.c
else
    SRCS += same_func2.c
endif

ifeq ($(EN_OP1), 1)
    CFLAGS += -DOP1=1
endif

OBJS=$(patsubst %.c, %.o, $(SRCS))

$(TARGET): $(OBJS)
  $(CC) -o $@ $^
  gdb -ex 'file $@' -ex 'info line func1' -ex 'quit'

%.o: %.c
  $(CC) $(CFLAGS) -c $^

clean:
  rm *.o *~ $(TARGET) -f

測試結果

前面提過,func1()的實作受兩個變數影響,分別為

  1. 巨集OP1是否有設定
    • 只有OP1 被設定並且非零時才會進入OP1func1()
  2. Makefile變數USE_FILE是否有設定
    • 只有USE_FILE為1的時候才會使用same_func1.c,其他情形都編譯same_func2.c

所以我們make 指令有下面四種變化

  1. 巨集OP1USE_FILE都沒設定
  2. 設定巨集OP1USE_FILE沒設定
  3. 巨集OP1沒設定,但是設定USE_FILE
  4. 全部設定巨集OP1USE_FILE

巨集OP1USE_FILE都沒設定

gdb驗證結果的確是

  1. 編譯same_func2.c
  2. 使用非OP1版本的func1()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ make clean
default settings: OP1 disable and use same_func1.c
rm *.o *~ test_same_func -f

$ make
cc -Wall -Werror -g3 -c test_same_func.c
cc -Wall -Werror -g3 -c same_func2.c
cc -o test_same_func test_same_func.o same_func2.o

$ gdb -ex 'file test_same_func' -ex 'info line func1' -ex 'quit'
...
Line 11 of "same_func2.c" starts at address 0x400597 <func1> and ends at 0x40059b <func1+4>.
Hello World
same_func2.c: func1_NOT_OP1

設定巨集OP1USE_FILE沒設定

gdb驗證結果的確是

  1. 編譯same_func2.c
  2. 使用OP1版本的func1()
1
2
3
4
5
6
7
8
9
10
11
12
13
$ make clean
rm *.o *~ test_same_func -f

$ make EN_OP1=1
cc -Wall -Werror -g3 -DOP1=1 -c test_same_func.c
cc -Wall -Werror -g3 -DOP1=1 -c same_func2.c
cc -o test_same_func test_same_func.o same_func2.o

$ gdb -ex 'file test_same_func' -ex 'info line func1' -ex 'quit'
...
Line 5 of "same_func2.c" starts at address 0x400597 <func1> and ends at 0x40059b <func1+4>.
Hello World
same_func2.c: func1_OP1

巨集OP1沒設定,但是設定USE_FILE

gdb驗證結果的確是

  1. 編譯same_func1.c
  2. 使用非OP1版本的func1()
1
2
3
4
5
6
7
8
9
10
11
12
13
$ make clean
rm *.o *~ test_same_func -f

$ make USE_FILE=1
cc -Wall -Werror -g3 -c test_same_func.c
cc -Wall -Werror -g3 -c same_func1.c
cc -o test_same_func test_same_func.o same_func1.o

$ gdb -ex 'file test_same_func' -ex 'info line func1' -ex 'quit'
...
Line 11 of "same_func1.c" starts at address 0x400597 <func1> and ends at 0x40059b <func1+4>.
Hello World
same_func1.c: func1_NOT_OP1

全部設定巨集OP1USE_FILE

gdb驗證結果的確是

  1. 編譯same_func1.c
  2. 使用OP1版本的func1()
1
2
3
4
5
6
7
8
9
10
11
12
13
$ make clean
rm *.o *~ test_same_func -f

$ make EN_OP1=1 USE_FILE=1
cc -Wall -Werror -g3 -DOP1=1 -c test_same_func.c
cc -Wall -Werror -g3 -DOP1=1 -c same_func1.c
cc -o test_same_func test_same_func.o same_func1.o

$ gdb -ex 'file test_same_func' -ex 'info line func1' -ex 'quit'
...
Line 5 of "same_func1.c" starts at address 0x400597 <func1> and ends at 0x40059b <func1+4>.
Hello World
same_func1.c: func1_OP1

延伸資料