dpkg
全名是Debian package,望文生義可以知道就是Debian套件管理程式。使用過Ubuntu/Debian 應該看過deb
檔案。這邊列出幾個常用的功能。測試環境是Ubuntu 12.04.4
- 顯示套件狀態:
- dpkg -s 套件名稱
- 顯示套件安裝到系統的檔案分佈
- dpkg -L 套件名稱
- 列出套件說明
- dpkg -l 套件名稱
- 搜尋檔案屬於那些套件
- dpkg -S 搜尋的檔案如stdio.h
同場加碼:apt-file這個套件也可以查詢檔案屬於哪個套件。
dpkg
全名是Debian package,望文生義可以知道就是Debian套件管理程式。使用過Ubuntu/Debian 應該看過deb
檔案。這邊列出幾個常用的功能。測試環境是Ubuntu 12.04.4
同場加碼:apt-file這個套件也可以查詢檔案屬於哪個套件。
看Debian New Maintainers’ Guide順便整理一下Debain官方文件建議打包套件可以安裝的套件和文件。
懶人包如下:(不包含gpc)
1
|
|
身為組裝工,常常執行下面的指令
用了那麼久從來沒有思考過這是什麼樣的東西,慚愧之餘趕快惡補一下。
雖然C語言號稱高移植性,然而遺憾的是因為System call, library等介面早期並沒有規範。這造成使用C語言開發多平台軟體的時候需要針對不同的平台做許多的檢查。後來GNU針對這部份提出了autotool工具減輕開發的負擔。
如果懶得看元件簡介,請直接看Overoview結論
從Wikipedia可以看到GNU Build system的項目中有提到三個元件: * Autoconf * Automake * Libtool
從這邊可以看到configure
負責產生
config.h
configure.status
configure.cache
(optional)
套件中有幾個程式
autoscan
configure.ac
autoconf
configure.ac
中產生configure
或
呢?這和autoconf的版本有關autoheader
configure.ac
產生config.h.in
ifnames
套件中有兩個程式
Makefile.am
中產生Makefile.in
,讓configure
執行時可以產生Makefile
configure.ac
或configure.in
產生aclocal.m4
另外GNU 其他針對移植性輔助的套件有
GNU Compiler Collection
從Wikipedia的圖:Autoconf-automake-process解釋的非常清楚。
回到最前面,我們可以觀察到autotool的目的就是
為了達到這樣的目的,系統需要做到下面的功能
因此,開發者最少要告訴autotools 所需平台環境 和 產生Makefile 這兩項資訊。這也是configure.ac
和Makefile.am
存在的目的。也就是說,開發者需要自行設定configure.ac
和Makefile.am
。
而這些錯綜複雜的關係可以描述如下
configure.ac
(透過autoscan
或是自幹)autoreconf
吃configure.ac
和Makefile.am
產生confgure
, config.h.in
, 和Makefile.in
configure.ac
,呼叫aclocal
產生aclocal.m4
configure.ac
和aclocal.m4
,呼叫autoheader
產生config.h.in
configure.ac
和aclocal.m4
,呼叫autoconf
產生configure
configure.ac
和aclocal.m4
和各處的Makefile.am
,呼叫automake
產生Makefile.in
範例程式細節在這邊,檔案各別重新分配到src
, include
, libs
這三個目錄。不想看code只要知道每個檔案都有參考到某個自訂的header file就好了。
1 2 3 4 5 6 7 8 |
|
簡述一下流程
autoscan
產生configure.ac
範本configure.ac
Makefile.am
,根目錄以及需要編譯的地方都要一份autoreconf --install
產生檔案
aclocal
autoheader
automake --add-missing
autoconf
configure
make
make install
安裝或make dist
打包執行autoscan
後會產生configure.scan
檔案,把這個檔案rename成configure.ac
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 |
|
接下來就是更動configure.ac
,更動完和原本的差別如下:
1 2 3 4 5 6 7 8 9 10 |
|
簡單說明如下
AC_CONFIG_SRCDIR
foreign
表示這不是標準的GNU Coding Standard,因此不會檢查額外的檔案如NEWS
, README
, ChangeLog
等。AC_PROG_RANLIB
表示要使用static library前面提到,每個目錄都要有一個Makefile.am
。關於Makefile.am
語法,可以參考 Alexandre Duret Lutz: Autotools Tutorial大略了解。更詳細的部份可以看Automake手冊:8.3 Building a Shared Library
範例中Makefile.am
如下:
1
|
|
1 2 3 4 5 6 |
|
最上面形式就是要裝到目錄
_Keyword
,所以表示我們要產生liba.a
和libb.a
,安裝時放在/usr/local/lib
下面、指定library由哪些原始程式檔案組成、同時要把header files放在/usr/local/include
下。(configure沒指定預設prefix為/usr/local)
1 2 3 4 5 |
|
一樣的形式:要裝到目錄
_Keyword
。這邊宣告要安裝到/usr/loca/bin
、要link liba.a
和libb.a
、並且5指定執行檔的原始程式檔案。
經過上面的處理,我們多了三個Makefile.am
和configure.ac
以及一些暫存檔案。新的樹狀目錄列表如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
configure
並編譯如前所述,直接使用autoreconf --install
就可以了
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 |
|
./configure結果節錄如下
1 2 3 4 5 6 7 8 9 10 11 |
|
幾點要注意的:
config.status
被產生,並且被執行時生出config.h
以及各目錄的的Makefile
/tmp/build
,因此make install
可以看到被安裝目錄樹狀結構如下:1 2 3 4 5 6 7 8 9 |
|
強烈推荐 Alexandre Duret Lutz: Autotools Tutorial,主要的想法和資料都是從這邊出來。而且他寫的更加詳細、更加易懂。
inline function是一個keyword,提醒compiler可以將function本體直接填入呼叫該function的位置。從這邊可以看到,優點為
廢話少說,先來一段程式碼。 測試環境
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
可以正常編譯
1 2 |
|
有趣的是,換成C99就會編譯錯誤,檢查symbol的確不存在。
1 2 3 4 5 6 7 8 9 10 |
|
從這邊可以看到C99的inline
定義是和GNU C的extern inline
相反。所以最簡單的懶人法就是在C99裏面加上extern
收工。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
不過對於組裝工而言,為何要在inline
前面加extern
或是static
實在有趣,所以多測了幾下。沒興趣的就直接看結論吧
如果我們把程式碼改成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
在GNU C下編譯執行會印Hello World 2
,使用objdump -S
反組譯可以看到
4004f4
位址4004f4
真正的程式碼是印出Hello World2
,也就是說extern inline void hello()
裏面的程式碼是寫心酸的。另外一點有趣的是C99下面把extern inline
和inline
對調會編譯失敗。
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 |
|
extern
定義相反。inline
只是一個宣告,不會產生symbol。要使用extern
編譯器才會產生symbol。猜測可能單純inline
是在header file宣告用,而extern inline
則是在source code實作時使用。前面討論了Linux中使用C語言載入data object 檔案資料,裏面提到資料轉成object,裏面會有三個symbol: _binary_objfile_start
, _binary_objfile_end
, _binary_objfile_size
。
man objcopy
找_size
可以看到
1 2 3 4 5 6 |
|
簡單來說,和平台無關的檔案轉成obj檔時,可以透過_binary_objfile_start
, _binary_objfile_end
, _binary_objfile_size
去存取資料。所以我再修改了一下測試程式,印出這些symbol 的內容
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 |
|
而測試檔案為
1
|
|
一跑起來會發生Segmentation fault如下
1 2 3 4 |
|
使用gdb可以看到當在存取_binary_my_data_txt_size
這行,我們還可以進一步來看這個變數
1 2 3 4 5 6 7 8 |
|
疑?變數位址是0x7?那我把測試檔案內容改一下,增加3個字元,再跑一次gdb。
1
|
|
1 2 |
|
可以看到位址從0x7
跑到0xa
,也就是10,這表示這個數字增加三了。這讓我懷疑這個symbol並不是一個變數,而是一個數值。因此我們可以將
* printf("_binary_my_data_txt_size: %d\n", _binary_my_data_txt_size);
改成
printf("_binary_my_data_txt_size: %p\n", &_binary_my_data_txt_size);
另外一個有趣的地方是_binary_my_data_txt_end
並不是C
,而是\0
。而_binary_my_data_txt_end
前一個字元是\n
。使用ghex
去看my_data.txt可以看到最後一個資料其實是\n
。但是\0
怎麼出現的,目前還不清楚。
man objcopy
之前看別人的程式,看到作者在ROMFS實作時把資料轉成object檔案,然後在C語言中直接存取資料。他的作法是
這個project使ARM的架構Realtime 作業系統。看完手癢也想看看Linux下面要怎麼做到類似的功能。本來想說是不是要動到link script,後來看到這邊的參考資料後,發現只要存取symbol就可以使用了。克服這個問題後就可以來寫個小程式驗證一下。
首先我們先弄個測試資料,很簡單就是一個數字顯示後面字串長度後再存放字串。
1
|
|
接下來就是Makefile部份
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
Makefile說明:
* patsubst
* Makefile 提供的函數,用在pattern 替換,所以上面做了下列的代換
* test_obj轉成test_obj.o
* $@
* Makefile的內建巨集,代表target
* $^
* Makefile的內建巨集,代表prerequisite
重點是把資料檔案轉成object的部份,節錄該部份並說明如下
objcopy -I binary -O $(BFN) -B $(ARCH) --prefix-sections '.mydata' $^ $@
-I binary
-O elf64-x86-64
-B i386
--prefix-sections '.mydata'
至於要怎麼知道binary格式和架構呢?您可以用objcopy --info |less
配合file test_obj.o
找出平台上的參數。
我們先來看一下section是不是真的放到.mydata?
1 2 3 4 5 6 7 8 |
|
我只知道.mydata的section,裏面的資訊目前還不清楚有什麼意義。
我們再用nm來看檔案內有哪些symbol
1 2 3 4 |
|
flag說明
* D
* 資料放在初始的資料section
* A
* Symbol的值已固定,之後link也不會更動。目前還不知道這樣透露出有什麼額外的資訊
接下來就是存取的方式了,測試程式如下。主要就是從section讀出字串再印出來。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
程式說明如下
* 前面nm看到的symbol _binary_my_data_txt_end
, _binary_my_data_txt_start
, 和_binary_my_data_txt_size
只是一個sybmol,這邊我們存的是字元所以宣告成char
* _binary_my_data_txt_start存放的是該section開始的資料,我們可以用gdb驗證一下
1 2 3 4 |
|
最後執行結果如下
1 2 |
|
C語言學習者第一個程式
1 2 3 4 5 6 7 8 |
|
最近有人在問,去掉{
}
你真的了解每一行嗎?我試著回答一下
#include <stdio.h>
int main(int argc, char **argv)
printf(“Hello world\n”);
return 0;
#include <stdio.h>
使用cpp hello.c > hello.i
可以看到1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
直接編譯hello.i並執行
1 2 3 |
|
* int main(int argc, char **argv)
從前面文章可以看到gcc編譯時除了link library和本身的object 檔案外,還會多link一些binary,可以從這邊找看看main在那邊。
說明一下,nm顯示的資料中
U
:表示undefined symbol
T
:表示symbol在Text section中
1 2 3 |
|
可以看到在crt1.o有一個Undefined symbol叫main,在來看看我們的hello.c這邊
1 2 3 |
|
所以可以看到crt1.o會用到main(),當然誰去呼叫main這件事我們就視而不見吧。
另外crt1.o
可以從man gcc看到被稱為startup file。
strace
去觀察hello呼叫了哪些system call1 2 3 4 5 6 |
|
可以看到事實上printf使用write system call去讓OS印字串到螢幕上。而為什麼不直接用write(1, "Hello world\n", strlen("Hello world\n"));
呢?看TLPI(書)有提到主要原因是system call有代價的,而libc實作了buffer減少system call呼叫的次數。有興趣的可以使用man setvbuf
看看buffer的設定方式。
return 0;
可以看下面的程式碼1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
跑看看便知道
1 2 3 4 5 6 7 8 9 10 11 |
|
$?
:man bash
找?
可以看到是顯示上次執行回傳狀態。因此不難理解return的值是有人會接起來的。Makefile就使用這個特性判斷build code是否有問題,我們可以測試一下
1 2 |
|
1 2 3 |
|
1 2 3 4 5 6 7 8 9 |
|
以前一直以為ld單純就是把.a, .o轉成binary,簡單測試一下發現完全不是這樣。
測試的檔案除了Makefile以外,其他的和這邊一樣
將原本的Makefile部份
1 2 |
|
改成
1 2 |
|
一跑make就出現錯誤
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
ld 這邊再加上-lc又有其他的錯誤,看來的確是有東西隱藏在背面。因此需要有對照組,這時候gcc -v
就可以出場了
1 2 3 4 |
|
可以看到gcc呼叫collect2,而collect2會呼叫ld
1 2 3 4 5 6 |
|
最後的Makefile版本變成
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 |
|
結論如下
關於一個程式的binary要怎麼存放其實是很有趣的問題,我以前都沒有去想這個問題。後來當組裝工久了以後就忍不住會想知道這些。隨便想一下就有很多問題,例如:
這些問題列出來真的是「罄竹難書」,不過我想整體來說至少在Linux下面從binutils下手應該是沒錯。第一個問題應該和linker有關係。所以我先去看ld文件中的linker script,希望可以解決我的疑惑。就算和我的問題無關,至少可以留下一些中文參考資料,造福需要的朋友。
ld
是GNU linker的程式。ld
吃多個object (.o)檔或archive (.a)檔,將他們的資料relocate還有symbol reference資訊一併輸出到新的binary。link通常是compile產生binary的最後步驟。ld
在執行的時候依照Linker command language檔案描述去產生binary。ld支援不同的binary format (BFD: Binary File Descriptor)
每次link的時候,都會依照特定的命令去產生新的object檔。而這些命令就是linker script;換句話說,linker script提供一連串的命令讓linker照表操課。Linker script描述的命令有
因為每次link一定會依據linker script去link,所以當ld
沒有指定linker script的時候,系統會使用預設的linker script。而ld --verbose
可以顯示預設的linkder script。link時指定自幹的linker script則使用ld -T 自己的linker script
。
* loadable: 執行時該section是否需要被載入到記憶體
* allocatable: 先保留記憶體的一塊空間讓程式執行時使用,如.bss
;
分開,空白會被忽略.
可以用"
包住1 2 3 4 5 6 7 8 |
|
這個抄來的範例很簡單,只有一個命令SECTIONS
。SECTIONS
是用來描述執行的時候記憶體的規劃配置(layout)。
說明這個指令細節
.
表示記憶體位置counter,起始值為0。結束值則由linker 計算把所有input section的資料整合到output section的長度。而.
如果沒有指定明確的記憶體位址的話,就會被設定為上一個位址counter的結束位址。參考示意圖: (Jim Huang) How GNU Toolchain Works投影片 。{ *(.text) }
)存放到輸出object檔案的.text
區塊中。另外要注意的是ld會自動幫你處理alignment的問題,所以不用擔心section之間的aligment問題。
ENTRY(symbol)
ENTRY(_start)
,然後去反組譯隨便一個C編譯出來的執行檔,找字串_start
可以看到裏面又去呼叫了__libc_start_main@plt
。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
INCLUDE filename
filename
這個linker script。可以被放在不同的命令如SETCTION, MEMORY等。INPUT(file1 file2 ...)
GROUP(file1 file2 ...)
AS_NEEDED(file1 file2 ...)
INPUT
和GROUP
使用的命令,用來告訴linker說如果object裏面的資料有被reference到才link進來,猜測應該可以減少儲存空間。範例(未測試請自行斟酌):INPUT(file1.o file2.o AS_NEEDED(file3.o file4.o))
OUTPUT(filename)
gcc -o filename
一樣SEARCH_DIR(path)
-L path
一樣STARTUP(filename)
OUTPUT_FORMAT(bfdname)
objdump -i
列出支援的binary 檔案格式OUTPUT_FORMAT(default, big, little)
objdump -i
列出支援的binary 檔案格式TARGET(bfdname)
objdump -i
列出支援的binary 檔案格式REGION_ALIAS(alias, region)
MEMORY
命令中區塊的alias,一般來說,用在不同的平台需要相同的memory layout時可以使用。舉例來說,當有3個平台,記憶體layout都是相同,那麼可以
MEMORY
命令寫在個別的檔案如linkcmds.memoryINCLUDE
載入linkcmds.memory,並且直接使用alias當作一般的區塊使用。詳細的範例說明可以看這邊
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 |
|
ASSERT(exp, message)
EXTERN(symbol1 symbol2 ...)
FORCE_COMMON_ALLOCATION
OUTPUT_ARCH(bfdarch)
objdump -i
查詢支援平台INSERT [ AFTER | BEFORE ] output_section
1 2 3 4 5 6 7 8 9 |
|
linker script提供設定symbol數值的方法。要注意的是,這邊的symbol可以指一個全域變數、SECTION
命令中的location counter(就是.
開頭的資料如.text
)
使用方式介紹如下:
1 2 3 4 5 6 7 8 9 |
|
關於expression是三小後面會再討論。
1 2 3 4 5 6 7 8 9 10 11 |
|
從這邊可以看到幾種assign
floating_point
的symbol為0_etext
的值為輸入object檔案.text
合體後的offset,個人猜測可以理解成end of text。(回顧一下.
是offset counter)_bdata
的值為輸出object檔案.text
結尾的offset 的4的倍數位址。這邊透露兩個資訊
HIDDEN(要隱藏的symbol)
可以把他理解成加了static
的全域變數,也就是說這個symbol只在這個處理範圍中才能摸到。PROVIDE命令(symbol = expression)
PROVIDE_HIDDEN(symbol = expression)
這節很有趣,解答我的一些小問題。
* `00000000004005ed g F .text 0000000000000101 main`
l
: localg
: globalu
: unique global,GNU 用於ELF時的 symbol binding extenstion!
: 既是global也是localw
: weak symbol<空白>
: strong symbolC
: symbol 是一個constructor (不知道這邊constructor是指那個東西? )<空白>
: 一般 symbolW
: warning symbol (不知道是三小)<空白>
: 一般 symbolI
: 間接地reference其他的symboli
: relocate 時要處理的function<空白>
: 一般 symbolD
: dynamic symbol (不知道是三小)d
: debug symbol<空白>
: 一般 symbolF
: 這是一個functionf
: 這是一個檔案O
: 這是一個object<空白>
: 一般 symbolfoo = 100
runtime發生什麼事?
ptr = &foo
runtime發生什麼事?
foo = 100
和在程式碼中轉出的symbol如foo = 100
差別在那?
其實一開始是為了看懂這個命令才會想看linker script的。如果接觸過很小型的Embedded OS就會發現很多都是自幹linker script;而這些scripts主要的描述命令就是SETCION
。
好了,廢話少說,進入主題。SECTION命令的功用是
典型的SECTION命令長這樣子:
1 2 3 4 5 6 |
|
望文生義地猜測可以這樣理解: 輸出object有一些大方向的規範,並且分為不同的section,每個section有他自己的規範。
而sections-command
可以分為下面幾種功能
要注意的事,如果你自幹的linker script沒有描述輸出object檔案的setcion的話,linker會
1 2 3 4 5 6 7 8 9 10 |
|
其中output-section-command
的功能有
這邊很多術語需要先搞清楚,先列出來,希望之後可以看到解答
address是section的一個optional欄位,使用的記憶體空間為VMA。如果沒有指定的話,linker會依下面的方式設定輸出object檔案section 的VMA。該VMA會遵循section 的alignment規範。
region
的話就從region內剩餘空間開始位址MEMORY
命令定義硬體記憶區塊的話,從定義的區塊中挑第一個符合SECTION的區塊。再將address設成該區塊內剩餘空間開始位址address欄位因為可以使用exression所以可能有下面的陷阱
.text . : { *(.text) }
.text : { *(.text) }
這兩個差一個.
,意義就差很多。沒有.
那個,表示沒有設定address,所以就是設成locale counter,並且linker會保證alignment。而有.
的就表示hardcode成locale counter,所以有可能會有alignment的問題。另外一點要注意的設定後locale counter也會跟著改變。
這部份可以說是整個output-section-command
的重點,目的是告訴linker讀取輸入object檔案後,怎麼把這些檔案裏面的section複製到輸出object檔案裏面適當地section。
格式為檔案(section1 section2 ...)
,檔案支援萬用字元。
所以常看到的*(.text)
的意思是:所有輸入object檔案裏面的.text
section。
指定多個section的方式有兩種
*(.sec1 .sec2)
:如果輸入object有兩個檔案的話,輸出object檔案裏面section會變成
*(.sec1) *(.sec2)
: 如果輸入object有兩個檔案的話,輸出object檔案裏面section會變成
在Makefile 自動將產生的檔案指定到特定目錄中,我犯了一個錯誤,造成更動header檔案發生錯誤(原文已修復)。
狀況如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
從錯誤中可以看到,明明在編liba.c code怎麼會有libb.h來亂呢?請再看下面的分析。
有問題的Makefile如下
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 |
|
問題出在$(CC) $(CFLAGS) -c $^ -o $@
,再次回顧一下
1 2 |
|
而在Makefile中我們會使用-include $(DEPS)
include 這些*.d檔案,所以更改Makefile部份敘述。
1 2 3 |
|
第一次make後,當header改變, 有的prerequisite會更動,進而觸發Pattern Rule,但是$^
是所有的prerequisite,所以$(CC)會把所有的prerequisite全部帶入造成錯誤。已這邊的例子,prerequisite就是libs/liba.c
和include/libb.h
了
解法很簡單,把$^
換成$<
就可以了。而$<
是什麼呢?就是第一個prerequisite,對照這邊可以看到,第一個就是第一個prerequisite就是libs/liba.c
檔案了。