UPnP 投影片上線
Ubuntu 14.04下使用dbdoclet 產生pdf手冊
Sept/1/2015 更新:下面的方法有個問題,目錄的頁面會有從ii
開始的羅馬數字頁碼。當known issue吧,猜測要從xsl修正,/usr/share/xml/docbook/stylesheet/docbook-xsl/fo/docbook.xsl
應該有線索。
前言
Javadoc是一個掃描Java原始碼自動產生網頁文件的工具。除此之外,Javadoc還提供了API讓人實作doclet的功能。Javadoc把產出資料餵給doclet,doclet自行決定該怎麼處理,如
- 轉成pdf文件
- 轉成LaTeX文件
- 轉成docbook文件
- …
回到主題,為了要產生PDF手冊,並且需要符合下面的要求:
- 封面
- 版權宣告、版本變動紀錄
- 目錄
- SDK簡介,包含方塊圖
- API使用方式
- 範例
doclet 評估
目前找到幾個免費產生pdf的doclet,簡單的試玩結果如下
- pdfdoclet
- 直接輸出pdf,沒有目錄、針對手冊需求增加修改資料極度困難
- AurigaDoclet
- 和樓上的主要差別在多了目錄
- ltxdoclet
- 產出LaTeX檔案,可透過pdflatex輸出pdf。有亂碼,美觀程度不如上面兩個
- TeXdoclet
- 產出LaTeX,不支援pdflatex,直接放棄。
- dbdoclet
- 產出docbook,透過一些方式可以達到目標,雖然美觀程度還是比不上pdfdoclet和AurigaDoclet,但是比doxygen和ltxdoclet好,最後用這個產生手冊。
- 其他族繁不及記載
使用dbdoclet 產生pdf手冊步驟
詳細指令大家自行估狗,這邊單純提供key word。大略步驟如下:
- 透過任何方式(openoffice, LaTeX, etc)產生封面、版權宣告的pdf的檔案,假設為
cover.pdf
。 - 撰寫針對你要產出手冊的XSL style sheet,我寫了三個東西。這邊要注意的是建議直接在XSL import docbook.xsl。
- 人肉產生page break的指令,原因docbook不夠聰明,有些東西還是得人肉加page break。如一個段落之後就是範例程式的程式碼。這時候範例程式的程式碼放在頁面開頭的話,閱讀應該比較輕鬆。
- header的描述
- footer的描述,可塞入圖片
- 以docbook格式撰寫你要塞的內容如
- 簡介
- 範例程式
- 透過javadoc執行dbdoclet,我這邊跑完會產生
Reference.xml
。 - 使用sed或是你順手的字串工具
- 修改產出的
Reference.xml
,找對的地方include自己寫的內容 - 如果有必要,修改針對你要產出手冊的XSL style sheet
- 修改產出的
- 透過
xsltproc
產生fo檔。幾點要注意的- 要加–xinclude,不然程式不會去include你自己寫的xml
- /usr/share/xml/docbook/stylesheet/docbook-xsl/fo/docbook.xsl不用指定,因為你自己寫的XSL style sheet已經import了,這個問題卡了我一陣子時間。
- 使用
fop
將fo轉成pdf檔,假設為draft.pdf
。 - 使用
pdftk
做- 把你的
draft.pdf
目錄前面頁面全部幹掉,另存新檔如temp.pdf
- 把
cover.pdf
和temp.pdf
合併,就會有封面和含目錄以及你設定的手冊了。
- 把你的
Ubuntu 14.04下使用Doxygen 產生pdf手冊
一個pdf程式SDK手冊,你會期待有
- 封面
- 版權宣告、版本變動紀錄
- 目錄
- SDK簡介,包含方塊圖
- API使用方式
- 範例
Doxygen是一個open source的文件產生工具,預設是產生HTML。不過它也可以產生很正規的文件,快速紀錄一下,細節還是要自行補完。要達到上面的目的另外前題是你要會估狗查詢使用LaTeX的用法。
- 安裝doxywizard
- 安裝doxygen-latex
- 執行
doxywizard
,透過GUI設定- 文件輸出目錄
- 原始碼目錄
- 開啟latex輸出
- (需要塞範例程式) export -> Input -> EXAMPLE_PATH
- 細項設定完成後,存檔。
- 到輸出文件的latex目錄中,開啟refman.tex。剪貼
%--- Begin generated contents ---
之前的文字,另存成header.tex%--- End generated contents ---
之後的文字,另存成end.tex
- 重新執行
doxywizard 你剛才存的Doxyfile
- expert -> LaTeX -> 打開
- LATEX_HEADER -> 選你剛才存的header.tex
- LATEX_FOOTER -> 選你剛才存的end.tex
- expert -> LaTeX -> 打開
- 存檔
- 修改header.tex,可以加入
- 封面
- 版權宣告
- 你產生API文件前的任何東西如簡介,方塊圖,貓的照片等
- 自訂的header/footer
- (需要塞範例程式)
- 在程式碼註解塞入
\example 範例程式檔名
- 將
範例程式檔名
存到前面EXAMPLE_PATH的路徑中
- 在程式碼註解塞入
doxygen 你剛才存的Doxyfile
- 到輸出文件的latex目錄中
- make,目錄由程式自動產生
- 開啟refman.pdf,檢查輸出是不是你要的,不是的話回到第八步
雜記: What a C Programmer Should Know About Memory
出處: What a C programmer should know about memory
這是筆記,不是導讀。單純放我覺得特別的東西和感想。不會自我要求可讀性和文章架構,請自行斟酌。
Understanding virtual memory - the plot thickens
The virtual memory allocator (VMA) may give you a memory it doesn’t have, all in a vain hope that you’re not going to use it. Just like banks today. 幹原來銀行是虛擬記憶體
malloc給valid pointer不要太高興,等你要開始用的時候搞不好OS給個OOM說人肉鹹鹹。簡單來說就是一張支票,能不能拿來開等到兌現才知道(煙)
Understanding stack allocation
原來C99的variable length array的運作是因為stack frame的特性,反正你要多少stack在橋的時候順便加一加。malloa一樣的原則。又學到了
When to bother with a custom allocator
- 今日英文(?): GP allocator, General purpose allocator,你每天用的malloc是也。什麼,沒用malloc過啊,還真是幸福(煙)。
Slab allocator
- 有的時候程式會allocte並使用多個不連續的記憶體區塊,如樹狀的資料結構。這時候對於系統來說有幾個問題,一是fragment、二是因為不連續,無法使用cache增快效能。
作者posix_memalign()可解決這類的問題。目前看範例,看不懂,感覺上和malloc很大的記憶體空間,自己管理差不多。先跳過,有大大路過可以留言解惑一下。
Demand paging explained
Linux系統提供一系列的記憶體管理API
- 分配,釋放
- 記憶體管理API
- mlock,禁止被swapped out
- madvise,提供管道告訴系統page管理方式如
- MADV_RANDOM,期待記憶體page讀取行為是隨機的。
laze loading:allocate記憶體先給位址。等到process要存取的時候OS就會發現存取到沒摸過的記憶體,於是就產生page fault,這時候才去處理page分配的問題。
每次page fault 就像kernel發動白金之星,然後就有一個白痴驚訝地發現自己樓梯怎麼走都走不完,但是他完全無法感受/了解發生了什麼事。
Fun with flags memory mapping
- long page_size = sysconf(_SC_PAGESIZE);
- 取得系統的page size。
Fixed memory mappings
投降輸一半,不懂Fixed memory mappings是啥、以及什麼時候需要這樣的東西。
File-backed memory maps
透過mmap,可以將檔案內容mmap到記憶體中,如此一來可以加快存取速度。配合參數,多個process可以共用同一塊記憶體。不過衍生出來有幾個問題
- 如何寫回檔案
- man msync
- 其他方式:fsync/pwrite
- 如何調整檔案/記憶體長度
- man ftruncate
Copy-on-write
有些情況是一個process要吃別的process已經map到記憶體的內容,而不要把自己改過的資料放回原本的記憶體。也就是說最終會有兩塊記憶體(兩份資料)。當然每次都複製有點多餘,因此系統使用了Copy-on-write機制。要怎麼做呢?就是在mmap使用MAP_PRIVATE參數即可。
mmap不是萬能丹,極端情況下連續page fault可能會比原本的開檔獨資料還慢。
延伸參考資料,我都沒看
Using as 手冊筆記
先承認我自己很不滿意這篇,太亂了。只能當工具查keyword用。不過as 手冊的確就是指令和語法。原本是以英文字母順序說明,我只是把這些用自認的方式重新分類。很多地方也真的只有句意翻譯。就把他當作看手冊的導讀,有找的需要的再進去看手冊吧。
本篇只討論ELF部份,其他binary format跳過。
目錄
as
參數
只提幾個我有興趣的部份
-Z
:硬上,就算有錯誤照樣組譯沒有錯的部份。--gstabs+
:好東西,可以幫你加入debug資訊,然後直接用gdb除錯。- 如果檔案副檔名為
.s
,就是普通的組合語言原始檔。 - 如果檔案副檔名為
.S
,就可以使用cpp
(還記得c preporcessor吧?)來處理前置處理。
名詞解釋
symbol
:由字母、數字、和_
、.
、$
組成的字串。不得以數字開頭。label
:symbol
後面加:
.
開頭的symbol是gas 的directive
expression
:運算式,結果代表不是位址就是單純的數字- 原始碼不是以上的情況,由英文字母開頭組成的字串就是instruction
- 原始碼最後一行一定要是
\n
。目前網友Carl有提供為什麼這樣規定的link。
常數
- 字元常數:
'字元
- 顯示\:
'\\
- 字串:
"字串"
Section
一個連續的記憶體空間。這段連續空間都是為了處理某些單一特定的任務如執行程式碼、存放global變數等。
題外話,.bss
存在的目的是節省儲存空間,沒有初始的全域變數當然不需要在檔案中保留儲存空間。
undefined section
在組譯的時段只要位址無法決定的symbol,一律放到undefined section。然後祈禱linker幫你搞定。
relocation
前面的文有提到,linker功能之一就是把不同的object檔案黏成一個執行檔。要怎麼黏呢?
每個object 檔案的起始點都是address 0。由linker計算並設定每個object檔案最後在執行檔放置的address,避免這些object的內容互相覆蓋。
而linker要怎麼搬移和設定最後的位址呢?這是因為object檔案內已經有規範好的不同名稱的好幾個連續空間,也就是section。所以linker把這些object檔案中相同section名稱的連續空間搬到執行檔內相同名稱的空間,並且保證執行檔內這些section的空間也是連續的。而搬移的動作並設定section的runtime address就稱為relocation
。
Linker在relocation時需要考慮的問題,as也幫他處理了,這些問題是
- 目前這個位址要對應到object檔案的哪個地方?
- 這個位址會需要佔用多少byte的空間?不懂?int和char吃的空間總會不一樣吧。
- 目前位址對應到的是哪個section? 這個位址和對應section的offset為何?
- 目前的位址是絕對位址還是和program counter相對的位址?
另外要注意的是,大部分的位址可以表示成
1
|
|
Expression
expression的結果代表不是位址就是單純的數字。這些數字要嘛是絕對位址、要嘛就是某個section的offset。而expression之間可以有空白。
Empty expression
空白字元或是null,其值會被設為0
Integer expression
由一個以上的argument和operator組成的expression
Arguments
包含 symbols, numbers 或subexpressions,分別討論
- symbol:結果將會是 {section setction的offset數值},數值會是32位元的二的補數(就是有正負值啦)
- numbers:一般來說,是正整數。如果你要處理浮點數或是大數(超過32位元的數字)as會噴警告。你需要自己處理這種情況。
- subexpressions:指的是
- (expression)
- prefix operator 伴隨一個 argument
Operators
用來協助運算section中的offset位址。
- Infix Operators
- 就一般的binary operator如
+
,-
等
- 就一般的binary operator如
- Prefix Operators
-
:負號~
:補數,就是將argument的每個位元inverse
Infix也和C語言一樣,有優先順序、符號定義也大致相同,列出如下
- 最優先
*
,/
,>>
,<<
- 第二順位
|
,&
,^
,!
- 第三順位
+
,-
,==
,<>
,!=
,>
,<
,>=
,<=
<>
就是!=
- 最低順位
&&
,||
directives
重頭戲。directive又稱pseudo-ops,一律以.
開頭。照字面理解,這東西是用來協助使用開發,而不是真正的CPU instruction。這邊我只列出看得懂我感興趣的部份。有興趣請參考出處。另外和硬體相依的directive請參考這邊。
變數相關
.ascii "字串"
:可以用多個字串,中間以,
隔開。這些字串最終會被一起放在連續的記憶體中。.asciz "字串"
:和樓上的差別是字串後面會自動填\0
,和C語言的字串表示方式相同。.balign[wl] abs-expr, abs-expr, abs-expr
:和.align
差別在b是byte
,w是2-byte
,l是4-byte
。這代表什麼呢?代表要pad的數字(如果有指定的話)要注意fill byte數量。如.balignw 8, 0xbeef
。.byte expressions
:expression
數量可以從0個到多個,中間以,
隔開。這些expression
會依照順序排列。那麼要幹什麼用呢?你可以這樣玩。
1
|
|
.int expressions
.long expressions
- 上面兩個有同樣效果,
expression
為16-bit寬度。可以用,
隔開。和.byte
用法類似。長度以及order會和CPU架構相關。
- 上面兩個有同樣效果,
.hword expressions
.short expressions
- 上面兩個有同樣效果,
expression
為16-bit寬度。可以用,
隔開。和.byte
用法類似。
- 上面兩個有同樣效果,
.double flonums
:就浮點數,可以用,
隔開。和.byte
用法類似。表示方式要看target CPU架構。.float flonums
:就浮點數,可以用,
隔開。和.byte
用法類似。表示方式要看target CPU架構。.lcomm symbol, length
:為symbol
保留length
的空間,該symbol型態不會是global
,並且會被放在.bss
section。.octa 大數字
:為16-byte寬度。可以用,
隔開。和.byte
用法類似。.quad 大數字
:為8-byte寬度。可以用,
隔開。和.byte
用法類似。.string "字串"
:將字串放到object file中,看不出來和.ascii
差在那。.string16 "字串"
:將字串放到object file中,字串中的單個字元將會展開成2個bytes。看不出來和.ascii
差在那。.string32 "字串"
:將字串放到object file中,字串中的單個字元將會展開成4個bytes。看不出來和.ascii
差在那。.string64 "字串"
:將字串放到object file中,字串中的單個字元將會展開成8個bytes。看不出來和.ascii
差在那。.set symbol, expression
:將symbol
的值設成expression
的值。.size symbol, expression
:設定symbol
空間為expression
的值。
Symbol的描述
visibility:local, global or weak
.extern
:單純是相容性使用,特地列出來只是因為手冊說as將所有undefined symbols
視為extern
.global symbol
.globl symbol
- 以上兩個同樣效果,就是讓
linker
看得到這個symbol
,也就是說透過nm
觀察binary也可以看得到這些symbol
。
- 以上兩個同樣效果,就是讓
.local symbol
:讓linker
看不到這個symbol
。手冊上另外有提到.local
不支援alignment的問題和解法。我看不懂,有興趣自行去連結參考。.weak symbol
:組譯器找不到symbol
會產生一個。
Symbol type
.type symbol, type
:type 描述方式有五種。我只用我看順眼的那種說明。"function"
:這個symbol
是個function"object"
:這個symbol
用來存放資料"tls_object"
:這個symbol
用來存放thread local資料"notype"
:沒有指定"gnu_unique_object"
:保證該symbol
是唯一的symbol"gnu_indirect_function"
:看不懂
其他Symbol 相關
.desc symbol, abs-expression
:提供描述symbol的特性,細節請參考前面的說明。.equ symbol, expression
:將symbol
設成expression
的值.equiv symbol, expression
:和上面類似,但是如果該symbol
之前已經定義過,就會噴錯誤。
Section
.data
:不解釋.test
:不解釋.section name
:讓as把以下的東西組成name
的section。名字雖然可以亂取,但是也要看binary format有沒有支援。如a.out
就沒有這東西。
ELF 下的Section directive
ELF的話,這個directive有加料。說明如下: []
表示optional
.section name [, "flags"[, @type[,flag_specific_arguments]]]
flags
:可由下面的flag合體組成a
:allocatable,就是要在記憶體內吃空間,但是loader不一定會載入東西到該sectione
:非executable或是shared library的sectionw
:可寫入x
:可執行M
:可被mergeS
:該section有 zero terminated 字串G
:屬於某個section groupT
:給thread local存放東西用 (存放三小?)?
:看不懂,跳過
type
由於@
在某些平台如ARM上是註解的符號,這種情況需要用%
替代。
G
和M
有特別規範,必須隔離在雙引號外面。而同時要用這兩個flag要以MG
順序擺放,範例如下:
.section name , "flags"MG,...
Section group目前先假裝沒看到,有機會又看到再回來討論。
條件以及控制相關
- if 部份有點雜亂,懶得想範例測試,想像成C語言的
#ifdef
。剩下自己看手冊。 .irp symbol,values...
:和巨集概念很類似,把.irp ...
到.endr
之間的instruction用到symbol
的部份全部換成value。範例如下。
.irp item, 2, 3, 4 mov %r\item, $\item .endr
會展開成
1 2 3 |
|
.irpc symbol,values...
:手冊上面的說明幾乎和irp
相同,悲劇的是範例和.irp
完全一致。唯一差別是.iprc
中有提到character,只能猜測c是character。.offset loc
:將locale counter設定成loc。.org new-lc, fill
:同樣是更動locale counter,但是只能在同一個section中移動。另外一個要注意的是這個指令只能增加locale counter,硬要減少是不可能的。當locale couter移動後,中間的空白會填入fill
的值。不加上, fill
as會填0。.rept 次數
:重複.rept
到.endr
指定的次數。.skip size, fill
:產生size
長度,fill
值的資料。.fill repeat, size, value
:產生value
,佔用空間為size
。是否要產生多個,否的話repeat
填0
,是的話repeat
填要產生的個數。size
和value
為optional,size
預設為1
,value
預設為0
。.fill 2,,
.fill 2,,10
.fill 2,4,
.warning "string"
:印出警告訊息。.err
:噴錯誤,除非as有-Z指令,不然別想產生obj檔。.error "錯誤訊息"
:印出錯誤訊息然後GG。不帶錯誤訊息as會印出檔案名稱和用了.error
那行。.fail expression
:expression
值大於五百噴警告,小於五百噴錯誤。用在複雜的巢狀巨集或是條件式組合語言中。.print "字串"
:組譯的時候stdout會印出字串。.end
:表示組合語言程式結束
巨集
跳過,自行看手冊
ELF相關
.symver symbol, symbol2@nodename
:指定symbol的版本號碼,一般用在shared library中。詳細說明懶得看,那天GG再回來看。
ELF section stack
.subsection name
:把目前的section push到section stack中,並且把目前的subsection置換成name
。.popsection
:從section stack中pop最上面的section去覆蓋目前的section.pushsection name [, subsection] [, "flags"[, @type[,arguments]]]
:把目前的section push到section stack中,並且把目前的section置換成name
以及subsection
,type
和argument
和.section
的參數相同。
ELF visibility
.protected symbol
:不但外部看不到該symbol,連內部要使用讀取該symbol的另外一個symbol也要在內部定義。直接舉個虛擬C語言。
1 2 3 4 5 |
|
.hidden symbol
:想像C語言在function前面加上static
,觀念類似,讓該symbol無法被其他component看見。手冊這樣的symbol通常被視為.protect symbol
,目前懶得寫程式測試。單純猜測這兩個有不同,不然幹嘛要分成兩個指令。.internal symbol
:手冊上提到除了和.hidden
有同樣效果外,不同的CPU會針對這個symbol做特別處理,到底是哪些特別處理,手冊沒說。
除錯相關
大部分跳過,太多背景需要補完。
.def
.endef
.dim
:給compiler產生除錯用。.file 檔案行號 檔名
:DWARF2用的除錯,除錯時對應的原始碼行號。.func name[,label]
:只有開啟除錯有效,必須在結尾加入.endfunc
。label
就是組合語言內的label
,也就是該function的進入點。不填的話,就在name
加上prefix 字元當作進入點,通常prefix字元為_
。.loc fileno lineno [column] [options]
:DWARF2用的除錯。整理如下- 手冊假設我們很瞭debug內部資訊,但是我不會。看下來他們有提到
.debug_line
狀態機.debug_line
line number matrix- 不明暫存器:
is_stmt
register,isa
register等
- 手冊假設我們很瞭debug內部資訊,但是我不會。看下來他們有提到
- 資訊放在binary 的
.debug_line
section。 - 在debuger(?)載入
.debug_line
資訊時,讀到該行,會把參數fileno
,lineno
,等參數一併載入。 - options:
basic_block
:設定.debug_line
狀態為basic_block
prologue_end
:設定.debug_line
狀態為prologue_end
epilogue_begin
:設定.debug_line
狀態為epilogue_begin
is_stmt value
:設定is_stmt
register 在.debug_line
狀態為value
,合法數值只有0
或1
。isa value
:設定isa
register 在.debug_line
狀態為value
,合法數值只有0
或1
。discriminator value
:設定discriminator
register 在.debug_line
狀態為value
,合法數值只有0
或1
。
.loc_mark_labels enable
:是否enble,basic_block register
,細節完全看不懂。只知道和debug line number entry有關。.stabs symbol, type, other, desc, value
:用來提供資訊給symbolic debuger。詳細資訊請看手冊.tag structname
:compiler產生的輔助directive。用來從symbol table中找出structname
的instance。.val addr
:看不懂。自己看手冊。看起來是紀錄addr
的值,但是怎麼會和symbol table扯上關係??未分類
.include "file"
:從目前的位置,把file
全部原封不動地放到之後的位置。.align abs-expr1, abs-expr2, abs-expr3
:local counter
(請參考前面linker script文)結束要對齊的位址倍數。abs-expr1
:必填。要對齊的數字。根據CPU,數字代表可能是byte,有些代表的是bit。所以要對齊8有的CPU要填8,有些CPU要填3。心理的OS,欠揍。abs-expr2
:optional。如果需要填空,可以指定填入的數值。不填就使用預設值,0。abs-expr3
:optional。指定跳過數字最多可以幾個,超過就直接不對齊了。(手冊用skip而不用pad讓我在想這到底差別在那?)
如果後面兩個都不想填,可以直接下.align abs-expr1,,
收工。
.comm symbol, length
:我是這樣理解啦,就是很多C語言檔案要用同一個全域變數。先摸到的先贏。可以看我以前整理的說明。另外有兩點要注意- 如果有同樣的symbol,在不同檔案中,設定的長度又不同,gas會選最大的。
- ELF有隱藏的第3參數,用來指定alignment。
.gnu_attribute tag, value
:GNU屬性自己查.ident 字串
:不同binary format有不同處理,在ELF中會把字串放到.comment
section中。要注意,file
包括command line參數中-I
指定的路徑。.incbin "file"[,skip[,count]]
:從目前的位置,把file
原封不動地放到之後的位置。你可以透過skip
指定從檔案起始地幾個byte後跳過。另外你也可以透過count
指定檔案最多include幾個bytes。
另外一點要注意,file
包括command line參數中-I
指定的路徑。
.version "string"
:產生.note
section並且將字串放入該section。
參考資料
C 語言的潛規則型態轉換
同樣是看過C Programming: A Modern Approach的筆記整理。一樣,寫的時候手上沒書,請自行斟酌,盡信書不如無書,更何況是組裝工亂寫的東西呢。
一般來說,變數型態可以強制轉換,這學過C 語言應該都知道。不過看了書上才發現不只強制轉換,compiler也會幫你的程式碼加料、做型態轉換。更恐怖的是,沒注意到的話,會發生慘劇。
本文開始之前,先定義專有名詞:
- promotion
- 當然不是升官還是促銷。而是把變數的型態升級,如short int -> int。為什麼要這樣幹呢?因為expression裏面變數不只一個,做運算的時候需要把某些變數升級,以免計算結果超過預期。諷刺的是,因為這樣的方式,也可能讓你的程式行為和你預期的不同。
書中提到潛規則型態轉換發生的時機如下
- expression中變數型態不同
- assign的left value和right value型態不同
- 函數中的參數變數型態和函數內部使用該變數時的型態不同
- 函數的回傳值和宣告的不同
後面兩種大概就是下面這樣的情況吧
1 2 3 4 5 6 |
|
書中用下面的方式分類C 語言的潛規則型態轉換規則,分別整理如下,偷懶不講C99了。
assignment
這個最簡單,等號左邊用啥型態右邊就得轉成這樣的型態。這也是為何
1 2 |
|
結果會是11的原因。
expression
一樣這是compiler的術語。此這邊就是一堆的歡樂的型態排列組合,C99還有_Complex
和_Bool
參戰。
一樣先分類
- expression中的變數只有小數點那種型態,依下面順序promote:
float
->double
->long double
- expression中的變數只有整數,就變成
- promote時要判斷使用
unsigned
還是signed
整數依照下面的順序promote:- …
int
->unsigned int
->long int
->unsigned long int
…
- …
- 這種情況沒注意的話,可能就會出現下面的悲劇
- promote時要判斷使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
執行結果如下
1 2 3 4 5 |
|
為何會GG呢,因為 i < j
是一個expression,照上面的規矩,expression同時有int
和unsigned int
的話,int
會被promote的unsigned int
,-1
的二進位不知道的人,可能要先搞懂再來學C吧?
如果你擔心程式有類似的問題,可以把gcc
最囉唆的檢查打開,就會噴出錯誤如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
你可能會問,那有小數點的型態和整數型態亂戰會怎樣呢?自己估狗或看書吧。
題外話
題外話一
由於這些潛規則,在轉換型態的時候,可以看到這樣的statement。
1 2 |
|
為什麼3
不用型態轉換呢?你必須要比對C語言的Operator precedence,也就是運算元處理順序。可以知道C 語法會處理順序如下
10
cast成double
型態- 因為潛規則,
3
也會被promote成double
- 計算
10 / 3
- 將結果assign給
=
左邊的變數
題外話二
在C語言中有小數點的常數預設型態可是double
唷。所以你如果有float
最好使用下面的方式轉換型態。至於為什麼會規定是double
呢?書中有講八卦,就不破梗了。
1
|
|
致謝
- 2016/Oct/28: 感謝網友
Jeffery Chang
糾正typo
C 語言的format String
format string也是看過C Programming: A Modern Approach才注意的。寫文的當下沒帶這本書,問男人好了。
1
|
|
簡單來說,format string是由
- 普通文字,不包含單獨的
%
字元 - conversion specifications,就是你看到的
%s
之類的的東西 - 每個conversion specifier有對應的參數
整理conversion specifier如下
- 結構
- 以
%
開頭 - 以conversion specifier 結尾
- 中間有些
特異功能optional的描述如 Conversion specifier
結尾,就是我們看過的d
,f
等
- 以
示意如下
%
[Flag][最少欄位寬度規範][精確度描述][變數size描述]Conversion specifier
接下來依上面的部份說明如下
Flags
#
- 自動對指定的數字進位數(八進位、十進位、十六進位等)加上合適的prefix如
0x
,0
等。
- 自動對指定的數字進位數(八進位、十進位、十六進位等)加上合適的prefix如
0
- 填入
0
作為pad。pad是啥呢?中文意思是填充物,自己體會吧。
- 填入
-
- 預設format string是靠右對齊,用了這個會變成靠左對齊。和樓上
0
一起會不會數字多好幾倍如432
變4320000
?不會,兩個同時出現0
會被省略。
- 預設format string是靠右對齊,用了這個會變成靠左對齊。和樓上
+
- 指定數字最前面要加正負號
' '
- 如果有設定數字要先顯示正負號,會在前面多加一個空白
以上是C 語言規範的,不同編譯器有加碼,懶得寫,自己問男人吧。
最少欄位寬度規範
一定要正整數,當印出的數字長度少於這邊指定的參數,會自動填空白或是0。印出超過這邊指定的寬度的話呢?超過就超過,不然要怎麼辦?
另外預設是往右邊對齊,想要往左對齊,請參考-
flag。
精確度描述
以.十進位數字表示
,不同的變數型態有不同的精確度定義。
舉例來說
- 整數,最少要出現的數字長度,少於這樣的數字,會直接在左邊或是基底符號(0, 0x)後面填零補完。
- 實數,也就是有小數點的,就是小數點後面最多可以出現的數字長度,超過了就截掉。
變數size輔助描述
一般來說,我們會知道d
是整數,但是在C語言還是有long int
,long long int
這樣型態的整數,為了能夠更精確的顯示,format string提供了這樣的描述,讓你加在Conversion specifier
,如
l
: longh
: short
詳細列表和排列組合請問男人。
其他
flag除了C規格定的以外,不同廠商有加料,一樣去問男人,不過要用這個的話要考慮porting的問題。
除此之外,男人有列出所有的Conversion specifier
,除了熟悉的d
, f
, s
, c
, x
以外,我列幾個我感興趣的如下
a
,A
: C99專用,實數的十六進位表示法p
: void * 的位址
怪招
format string還有*
和%m$
這種鬼東西,目前搞不清楚為什麼要這樣幹。
不過幾然花了時間搞懂,就整理一下
*
指目前對應的參數的下一個參數%m$*n$
把第n個的參數和第m個顯示交換
不知道是什麼鬼對不對?我也是,所以寫了程式測試一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
看看輸出吧,一樣懶得寫Makefile
1 2 3 4 5 6 |
|
結論
這篇文章文章我介紹了
- format string和他的語法簡單說明
- make 的implicit rule
- 取得Linux 下執行程式結束回傳值
參考資料
C 語言的逗號
因為只能和 C語言裝熟,只好看書看能不能裝更熟點。感謝成大同學推荐的書本:C Programming: A Modern Approach。今天看到逗號的用法,手癢來用一下。
簡單來說,,
就是把expression串在一起,然後回傳最後一個expression的值。注意的是expression是compiler專有名詞,自己看連結,不解釋。
知道這個特性,當然來個小實驗。照慣例還是要講一下測試環境。
1 2 3 4 5 6 |
|
接下來寫個小程式:
1 2 3 4 5 6 7 8 |
|
程式太簡單,懶得寫Makefile,直接make:
1 2 |
|
接下來就是驗收
1 2 3 |
|
接下來要問問題時間
- 為啥要這樣幹?
- 這個要用在這什麼地方?
先回答第一個問題,就是要把一堆expression擠到被認為是一個expression。有了這樣的想法後,很多語法會只有一個express,如if
, for
, while
,這時候想要在裏面多做事想得不得了的時候就可以用,
,不過和條件有關的情況下,記得注意僅回傳最後一個expression的特色。
題外話,這個,
目前我印象中除了變數宣告和for
以外,只看過一次,那次看到讓我有點反應不過來,感覺單純只是想省掉{}
而已。
Hello Linux ARM 組合語言
目錄
前言
之前的文章有不少在討論執行檔該長怎麼樣。簡單來說,一個執行檔會有
- Sections:程式行為和資料會分開放在不同的sections
- 進入點,也就是system call開始執行你的程式的地方
以這樣的觀點,來看組合語言,會比較有感覺。
這次主要想要試看看如何使用組合語言印出Hello world。學過作業系統的朋友應該知道OS真正提供給使用者的介面叫作system call。有興趣的朋友可以使用strace
研究執行檔呼叫了那些system call。這次的Hello world我有兩個線索
- 在command line執行的process會有3個馬上可以使用的file descriptor(不知道那啥的請自行估狗)分別是
0
: standard in1
: standard out2
: standard error
- 有一個system call叫作
write
,你可以透過他把任何資料寫到指令的file descriptor
綜合以上,我們要幹的事就是透過組合語言做出
1
|
|
這又表示組合語言中我們要做
- 呼叫system call
- 帶參數給system call,這部份需要有
- ABI的背景知識
- 定址方式,更精確的說,如何宣告
"Hello world\n"
,讓runtime時放在在process address space中,並將它的位址傳給system call
測試環境
- Host
1 2 3 4 5 6 |
|
- Guest OS on Qemu
- 這邊很奇怪我的kernel用更新過的版本Qemu完全無法開機。目前裝死中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
範例:版本一
我本來想說慢慢來,先來個完全沒意義的r0 = 0; r1 = 1; r2 = r0 + r1。程式如下:
1 2 3 4 5 6 7 |
|
幾點說明:
$
或是#
代表一個數字(出處)%r1
代表ARM的r1
暫存器,但是為何用%
目前沒找到手冊上有說明。.text
前面的文章有看應該覺得很眼熟,就是告訴編譯器以下是程式行為。global
是讓symbol可以外露,白話來說就是nm
等binutil可以看的到這個symbol。_start
是一個程式執行的起始點,有看過之前文章就會覺得很眼熟。.end表示程式結束點,不過目前用起來有沒有加好像沒有差別。
Makefile
1 2 3 4 5 6 7 8 |
|
想法很簡單,就是直接編譯應該可以跑,雖然完全不會有畫面。錯!跑出來會這樣
1 2 3 4 5 6 7 8 |
|
這代表什麼,hello編譯完後的binary本身沒有執行權限。改改權限看可不可以跑?
1 2 3 |
|
怎麼回事?分析一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
看不出來對不對?我是這樣啦,所以先比對/bin/ls
的輸出吧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
仔細看一下Type:
,/bin/ls
是EXEC
而hello
是REL
,man elf
可以看到REL
是relocate file,那是啥呢?根據System V Application Binary Interface - DRAFT - 10 June 2013第四章的簡介,簡單來說就是object檔案,也就是說link時吃的檔案。所以我們加入Link吧。
範例:版本二
單純加入linker看看會怎樣?
1 2 3 4 5 6 7 8 9 |
|
kerker,一樣GG。
1 2 3 4 5 |
|
估狗到的組合語言的Hello world範例最後會呼叫exit
system call,照著呼叫exit
就可以正常結束,這就是第三版。至於為何會出現Illegal instruction
,根據Scott Tsai大大的提示,當你的程式碼跑完後,接下來記憶體有啥CPU就跑啥,跑到不認識的opcode當然就GG了。
範例:版本三
1 2 3 4 5 6 7 |
|
單純叫了exit
而已,有幾點注意的
根據Debian ARM system call interface,可以知道
- r0 ~ r6是函數的帶入參數
- r7 是system call number,而system call number是啥呢?就是你要的system call 對應的數字。
所以要呼叫exit(0)
system 表示
- 傳一個參數,數值為
0
- 要設定
exit
對應的system call number為1
。
為何system call number是1
呢?可以看看unistd.h裏面system call number的定義,exit的system call number為1
。
設定完傳給exit
參數後呼叫了一個組合語言指令svc
,這個指令主要是切換到Supervisor模式。Linux下面也許太過複雜,不太容易從user mode一路追到kernel然後又看懂這些system call的行為。沒關係成大資工作業有比較看得懂的範例可以參考。例如包裝呼叫system call的函數以及Kernel中對應的system call服務實作。
根據unistd.h定義的system call number,write
的system call number 是4
,所以我們可以開始寫最後的版本了。
範例:版本四
開始之前,先來看男人怎麼介紹write system call的
1
|
|
這代表
- 有三個參數要傳給system call
- 有回傳值可以吃
- 其中一個參數是位址,這個位址我們會放
"Hello World\n"
字串
那麼我先來看看怎麼放字串到記憶體
1 2 3 |
|
.data
如果有看我以前的文章,就知道這是放有初始值全域變數的地方。
而hello_str
呢?嗯,對_start:
有印象嗎?
這叫作label,是GNU組語中symbol的一種,有興趣可以看這邊。根據手冊,label還有一個功能,代表目前跑到的位址。所以_start:
就是.text
section的起始位址。而hello_str
就是.data
section的起始位址。從這兩個label可以看到label只是一個位址,可以指向函數或是資料,這和C語言的指標有異曲同工之妙。有興趣的朋友可以去找function pointer和C語言的callback函數。
而.ascii
,單純就是宣告字串指令。
呼叫write
system call來還有兩個問題要處理
- 如何取得
hello_str
對應的位址放到暫存器r1
上面? - 要怎麼算出
hello_str
字串的長度?
關於第一個問題,GNU ARM組合語言有中將數值或位址放到暫存器的虛擬指令
1
|
|
expression是一種表示位置或是數值的方式。
恰巧symbol也算是一個expression,所以可以表示成:
1
|
|
第二個問題呢?要介紹.
了。之前看過linker script的朋友應該對於locale counter還有印象。locale counter代表目前的位置。加上expression
也支援運算。利用hello_str
是.data
開頭,我們可以這樣做:
1 2 3 4 |
|
綜合上面的討論,版本四組合語言會是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
等等,不是說有回傳值?還是要看Procedure Call Standard for the ARM Architecture
ABI r2.09手冊,上面提到回傳資料會放到r0
,剛好接下來的exit
system call帶的第一個參數也要存放在r0,那麼我們可以直接觀察執行後回傳值如下:
1 2 3 4 5 6 7 8 |
|
補充
1
|
|
這是個有趣的指令,這個指令事實上是個虛擬指令。怎麼說呢,因為這個指令的目標是把數值塞到指定的暫存器。這個數值是位址還是啥死人骨頭並不重要。重要的是,由於opcode的限制,把數值塞到指定的暫存器會有限制滴。例如ARM Cortex M0
的MOV
的數值只有8-bit,要塞32-bit的數值就需要配合其他的指令做連續技。因此
1
|
|
這樣的指令就可以讓你寫起來比較輕鬆。
另外一個值得一提是,如果組譯器無法把ldr <register> , = <expression>
虛擬指令轉換成MOV
或MVN
指令,把你的數值塞到暫存器的話。組譯器會把你的值放在一塊記憶體中,使用真的ldr把這塊記憶體的值載入到暫存器中。這個方法稱為literal pool,細節可以看這邊。
總結
本文從會GG的組合語言一路改到可以印出Hello World,並且在程式結束後回傳字串長度。在文章中簡單提到了GNU as的
- 組合語言中section
- 組合語言的編譯方式
- 組合語言中的symbol和字串表示方式
- 組合語言中的expression
- ABI實例
希望對有需要的朋友有所幫助。
延伸閱讀及致謝
感謝Scott Tsai大大提供的資料以及指出文章中錯誤的地方。另外他也有提到其他有趣的部份,當作以後的作業。先列出如下
- 從組合語言直接呼叫header file內的system call
- Kernel Memory Layout on ARM Linux
- 這邊主要討論的是
objdump -d
發現obj檔和執行檔差別只在進入點位址的差別,而進入點位址如何決定呢?這邊有規範,另外也可以看default linker script看看如何設定的。
- 這邊主要討論的是
- Scott大大的範例程式
- 可以看到,這個版本和我參考寫出來的版本差異有
- 把
"Hello world"
放在.rodata
section中,這比.data
更實際,因為這個字串的確沒有必要設成全域變數。 - 使用了preprocess方式。
- 提供了反組譯的結果
- 提供X86-64版本的組合語言比較
- 把
- 可以看到,這個版本和我參考寫出來的版本差異有
感謝Viler Hsiao大大寫文回答我的問題。
參考資料
- 『Hello World!』 in ARM assembly
- 本篇程式碼大量參考這篇。
- GNU Manual: Using as