Executable Format 主要有:

  • Windows 下的 PE(Portable Executable)
  • Linux 下的 ELF(Executable Linkable Format)

其他還有 Intel/Microsoft 的 OMF(Object Module Format)、Unix 的 a.out 以及 MS-DOS COM 格式等等。

除了可執行檔外,Dynamic Linking Library 跟 Static Linking Library 都是以 Executable Format 儲存。

ELF 標準中將使用 ELF 格式的檔案分成:

ELF file type Example
Relocatable File Linux 的 .o、Windows 的 .obj
Executable File /bin/bash、Windows 的 .exe
Shared Object File Linux 的 .so、Windows 的 .dll
Core Dump File Linux 的 core dump

ELF file structure

ELF 檔是由 header、一堆 section 及一堆 table 組成的,各 table 也是 section。

ELF structure

ELF Header

描述整個 ELF 檔的屬性,可用 readelf -h xxx 查看。

struct 定義在 /usr/inclue/elf.hElf32_Ehdr or Elf64_Ehdr

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf32_Half e_type; /* Object file type */
Elf32_Half e_machine; /* Architecture */
Elf32_Word e_version; /* Object file version */
Elf32_Addr e_entry; /* Entry point virtual address */
Elf32_Off e_phoff; /* Program header table file offset */
Elf32_Off e_shoff; /* Section header table file offset */
Elf32_Word e_flags; /* Processor-specific flags */
Elf32_Half e_ehsize; /* ELF header size in bytes */
Elf32_Half e_phentsize; /* Program header table entry size */
Elf32_Half e_phnum; /* Program header table entry count */
Elf32_Half e_shentsize; /* Section header table entry size */
Elf32_Half e_shnum; /* Section header table entry count */
Elf32_Half e_shstrndx; /* Section header string table index */
} Elf32_Ehdr;

e_ident 是 magic number 標示這是個 ELF 檔,前 4 個 byte 依序是 0x7f、0x45、0x4c、0x446,接著 3 個 byte 是 “ELF” 三個字母的 ASCII code。所有 ELF 檔前幾個 byte 都是這個內容。除此之外,幾乎所有可執行檔最開始幾個 byte 都是 magic number,供 OS 識別是哪種可執行檔。

e_phoff 表示程式執行時的入口位置,executable file 會填入 address,relocatable file 因為還會進行 relocate 所以值是 0。

從 section header table file offset e_shoff 可以知道 section table 所在位置,由 e_shentsizee_shnum 可以知道 section header table 的 element size 以及總共有多少 element。從 ELF header 可以找到 section header table,由於其他 table 也都是 section,可以再從 section header table 取得所有其他 section 及 table 的資訊。

Section Header Table

描述各 section 的屬性,可用 readelf -S xxx 查看。

一個以 struct Elf32_Shdr(又稱 section descriptor)為 element 的 array,array 的第一個 element 是 NULL,struct 定義在 /usr/include/elf.h。因為 section header table 是個 array,所以 ELF 檔有些地方會以 section 在 section table 中的 index 來 access 或表示該 section。

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct
{
Elf32_Word sh_name; /* Section name (string tbl index) */
Elf32_Word sh_type; /* Section type */
Elf32_Word sh_flags; /* Section flags */
Elf32_Addr sh_addr; /* Section virtual addr at execution */
Elf32_Off sh_offset; /* Section file offset */
Elf32_Word sh_size; /* Section size in bytes */
Elf32_Word sh_link; /* Link to another section */
Elf32_Word sh_info; /* Additional section information */
Elf32_Word sh_addralign; /* Section alignment */
Elf32_Word sh_entsize; /* Entry size if section holds table */
} Elf32_Shdr;

如果一個 section 存在 ELF 檔中,由 sh_offsetsh_size 可以知道其所在位置及大小。section 屬性主要由 section type sh_type 及 section flags sh_flags 決定。

String Table

.strtab 以及 .shstrtab section。

將 ELF 檔裡所用的字串,如 variable name、function name、section name 等存在一個 array 中,以 '\0' 隔開,並以字串在 array 中的 offset 表示該字串。

Symbol Table

.symtab section,可用 readelf -s xxx 查看。

記錄 object file 所用到的 symbol。每個 symbol 有其對應的 symbol value,variable 及 function 的 symbol value 是它們的 address。

symbol table 是以 struct Elf32_Sym 為 element 的 array,Elf32_Sym 一樣定義在 /usr/include/elf.h

1
2
3
4
5
6
7
8
9
typedef struct
{
Elf32_Word st_name; /* Symbol name (string tbl index) */
Elf32_Addr st_value; /* Symbol value */
Elf32_Word st_size; /* Symbol size */
unsigned char st_info; /* Symbol type and binding */
unsigned char st_other; /* Symbol visibility */
Elf32_Section st_shndx; /* Section index */
} Elf32_Sym;

對 linking 較重要的兩種 symbol:global symbol 及 external symbol。global symbol 是定義在此 object file 中並且會讓其他 object file 使用到的 symbol,external symbol 則是此 object file reference 到的其他 object file 中的 symbol。簡單講就是給別人用跟用別人的 symbol,有點繞舌。

Relocation tables

存 relocation 的資訊,.rel.text section。每個需要 relocate 的 section 都會有一個 relocation table。

relocation 可參考 Static Link

Linking View and Execuion View

上述以 section 來劃分 ELF 內容的角度是 Linking View

ELF 在 mapping 到 virtual address space 時是以 page 為單位,如果 section 的大小不是 page 大小的整數倍又以 section 為單位進行 mapping,會浪費許多 memory。load ELF 時 OS 只在乎 section 的屬性如可讀、可寫、可執行,OS 不在乎 section 的內容,為了節省記憶體空間延伸出「將屬性相同的 section 合併成一個 segment,再對應到 virtual address space」的方式。linker 在 link object file 時會盡量將相同屬性的 section 放在一起。

segment 是以 load 的角度劃分 ELF,也就是 Execution View

Ref

定義演算法家族,將個別演算法封裝起來,讓它們可以互相替換。此 pattern 讓演算法的變動不會影響到使用演算法的部分。

Strategy pattern 是一種定義一系列演算法的方法,所有演算法完成的都是相同的工作,只是實作不同,它可以以相同的方式 call 所有演算法,減少了各種演算法 class 與使用演算法的 class 間的耦合。

例子:最短路徑搜尋

Strategy Pattern 最短路徑搜尋 class diagram

將 shortest path algorithm 封裝起來,讓 RouteFinder 使用。RouteFinder 使用 ShortestPathStrategy 找 shortest path(RouteFinder 將找路徑的工作 delegate 給 ShortestPathStrategy),ShortestPathStrategy 的 instance 則依據使用哪種 algorithm 決定。

有新的 algorithm 或 strategy 時只要 implement ShortestPathStrategy 並換掉 RouteFinder 使用的 instance 即可。

使用 RouteFinder 的 client code 跟演算法 strategy 的 class 就是分開、不互相耦合的。

Ref

機械式開關切換時訊號會有 bounce(彈跳)現象。

switch bounce

上圖為理想狀況,下圖為實際上有 bounce 的狀況。bounce 持續時間約為 10~20 ms。

程式在 bounce 期間會讀取到 bounce 的訊號造成判斷錯誤。例如按一下按鈕開關我們期望程式只讀到一次 on 的訊號,bounce 會造成程式讀到很多次 on 的訊號。

解決方式軟體及硬體皆有,硬體的我看不懂所以這裡只說軟體。(欸)

軟體解決方式:不處理 bounce 時間內的訊號。「不處理」的實際做法可以自己硬寫也可以在 add_event_detect() 裡設定。

圖片來源:http://www.bbc.co.uk/schools/gcsebitesize/design/electronics/switchesrev2.shtml

接 button、switch 時會用到。(看到 button 這字想到的是 GUI 上的…)

電路中希望維持一個電位基準值,好判斷某個電壓值是 0 還是 1。pull-up 跟 pull-down 電阻就是用來維持基準電位的,如果電路沒有接個東西,程式會讀到雜訊(亂數值)。

Pull-up resistor

switch 斷開時上半部的電路是通的,logic gate 會讀到較高的電位。switch 接上後,logic gate 的電位會變低(從電流來看是下半部電路會接通,以至於電位會改變)。如果認定 switch 接上是邏輯的 1 則 logic gate 是低電位時表示邏輯的 1,高電位表示邏輯的 0。

Pull-down resistor

pull-down 電阻是反過來,switch 斷開時 logic gate 會讀到低電位,接上後是高電位。所以高電位表示邏輯 1,低電位表示邏輯 0。

接 Raspberry Pi 時,上面說的 logic gate 就是 Raspberry Pi 的 GPIO pin。

murmur:被高低電位搞得頭有點暈,不太確定那個電流的理解對不對?沒搞得很懂 logic gate 在乎的是電位還是電位差……我的電路學只有高中程度啊……Orz

圖片來源及 Ref:

最近的感想。

念書的時候常覺得書上的理論跟實際上的使用或實作有一段距離,我說不上來那是落差還是什麼,但就是有點搭得起來又哪裡怪怪的。後來工作後我有點疑問──在學校念的那些理論,真的有用嗎?如果業界是以實務為主,為什麼我們需要學那些理論?從這個問題就看得出來我當學生的時候很少好好理解過為啥自己要念那些書…

我最近得到的答案是──理論會提供我們背景知識與架構,可以讓我們能較快速的理解系統跟軟體的運作方式,而不至於迷失在茫茫程式碼海中,要從許多的片段中慢慢拼湊才能知道整個系統的樣貌。當然,理論提供的是偏向概念上的了解,它不會有太細部的東西,實務上的細節是會隨著情境不同而調整的。

軟體的結構也一樣。最近工作上,我發現因為對整個軟體的架構比較熟悉了,所以能比較快猜測、反應出可能是哪邊有問題,不像之前一遇到問題就得重新 trace,也似乎比較看得出來改動哪個部分會影響到那些東西。開始稍微能體會軟體有其架構的重要性,當它有個結構、有些規則可循,programmer 比較能知道改動的影響範圍,出問題時也能比較快找到。我們可以不用去記憶一些特例跟「這邊改了那邊還有那那邊也需要改」的事情,這也會減少出錯的可能。對我這種不喜歡記那麼多細節還會搞混的人來說這真是太棒了!

最近在看《深入淺出設計模式》,我想 design pattern 也是一種軟體上的結構,如果知道某個部分是使用某個 pattern,就能馬上了解其大致的運作。不過我不了解 pattern 的時候,看那些 code 只有一個感想──幹嘛把事情搞得那麼複雜?

可惜的是,我現在仍然不覺得「軟體有其架構帶來的效益」這件事能有精確量化的指標。因為沒有對照組,沒有一套沒有架構的同樣軟體去比較有架構與沒有架構的差別,可能只能用間接的方式去量度關於軟體有架構的益處。

封裝會改變的東西,減少改動一個部分會影響到其他部分的狀況。

將 class 中容易改變的部分封裝到另一個 class 有助於保護原本 class 有不必要的改變。

程式改變可能改動到相關功能,如果一個 class 中常修改的地方並非該 class 負責的事情,修改它是不必要的,而且可能引入其他功能受到影響的風險。如果能將常修改的地方封裝,就可以保護原本 class 的其他功能不受影響。

「封裝」不單指將東西包成 class,把一堆 property 放到 map 之類的 container 也是封裝。

A cohensive class does one thing really well and does not try to do or be something else.

來來來翻譯一下:一個 class 只做一件事,不會插手做別人的事。

但是,這「一件事」的大小跟規模是由設計的人定義的,可以很大也可以很小,定義清楚即可。

class 內聚力越高,class 間的耦合度(改動一個 class 就要改動其他 code 的程度)越低,越容易 reuse 及擴展。

讓一個 class 只做一件事,只有那件事情需要修改時才會讓這個 class 改變。這樣每次修改的影響範圍可以縮小,可能產生 bug 的範圍也就縮小啦。另一個好處是 debug 的時候,programmer 可以較迅速的知道可能出問題的範圍,降低時間成本。

檢查內聚力的方式:做一項改變時是否牽動到許多 class?是的話表示內聚力低、耦合度高,程式不易修改跟擴充。

Delegate 中文叫「委派」。

將某些操作(例如是否相等)的細節交給 object 自己處理。

用一點簡單的 code 來說明。首先是沒有 delegate 的寫法(這裡先不管 member 放在 public 不是好習慣的問題):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Foo
{
public:
Foo(int x, int y) : m_x(x), m_y(y) { };

int m_x;
int m_y;
};

int main()
{
Foo foo1(10, 20), foo2(200, 300);

if (foo1.m_x == foo2.m_x && foo1.m_y == foo2.m_y)
{
// do sth.
}

return 0;
}

這種方式,當在任何地方需要比較兩個 Foo object 時,都要像上面第 14 行這樣寫。不覺得這樣都把 Foo 肚子裡的東西挖出來到處亂放嗎?如果之後 Foo 要多加一個 member,所有比較 Foo object 的地方都要改,光是有 20 個地方要改就有得受了,更何況還可能漏掉勒。所以啦,delegate 的觀念就可以在這裡拯救可憐的工程師,以下是有 delegate 觀念的版本:

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
class Foo
{
public:
Foo(int x, int y) : m_x(x), m_y(y) { };

bool IsEqual(const Foo& rFoo) const
{
return (m_x == rFoo.m_x && m_y == rFoo.m_y);
};

int m_x;
int m_y;
};

int main()
{
Foo foo1(10, 20), foo2(200, 300);

if (foo1.IsEqual(foo2))
{
// do sth.
}

return 0;
}

增加 IsEqual() 後,我們就可以把判斷放進 class Foo,由 Foo 自己處理如何判斷相等的這件事。外面只需要 call IsEqual() 就可以比較兩個 Foo object,也就是外界將判斷相等的事情「委派」給 Foo。這時候如果多加一個 member m_z,只需要改 IsEqual() 的 implement 就可以了,外面 20 個比較的地方都不用改!世界變得一片美好。

文言一點來說,delegate 有助於保持 loosely coupled。loosely coupled 表示 object 彼此獨立(也可以看成 object 跟其他 code),對一 object 的修改不會引起一連串其他 object 或 code 的修改。

問題時間

  • 為什麼不用 C++ 的 operator==

    operator== 也可以。這裡想表達的是 delegate 的觀念,如何實作不是重點。反過來說,不同語言也有不同的特性跟用法,而一個觀念可以用很多種方式實作。

  • 為什麼感覺有封裝的味道?

    我也這麼覺得。一些 OO 原則跟觀念彼此根本一家親,運用 A 的同時也運用了 B。我暫時想不出更好的範例了,就先這樣吧!:P

設計軟體事先處理整體系統輪廓,將大系統切成多個較小的問題,再逐一反覆處理每個小問題直到完成整個系統。

Feature Driven Development 及 Use Case Driven Development 是「將系統切成小問題再逐一反覆處理」的方式。

Feature Driven Development

以 feature 為主軸,以功能為切入角度。

挑出特定功能,規劃、分析及開發該功能直到完成。

看系統的角度比較 granular。

適合功能較個別獨立、未密切相連的系統。

Use Case Driven Development

以 use case diagram 為主軸,以 scenario(使用情境)及流程為切入角度。從 use diagram 拿出一個個 use case 做,挑出 use case 的 scenraio,寫 code 支援該 scenario,直到完成 use case 的所有 scenario 以及完成所有 use case。

看系統的角度比較整體。

適合由一堆流程構成的系統,例如差勤請假系統。

碎念時間

實際上開發軟體會混合多種方式,例如從 use case driven 開始,接著在 use case 挑出小功能進行設計(這是 feature driven),最後在實作階段用 test driven 來思考如何 implement。

開發軟體的方式很多,各有其適用之所在,重點不在於哪個方法最好,而是哪些方法的搭配使用能較好的解決所面對的問題。

很久很久以前,在那個網路只有撥接還常常撥不上去、撥上去也不能用太久的年代,我開始學寫網頁。那時候網路上的資源沒有現在豐富,搜尋引擎還在用蕃薯藤跟奇摩。當時覺得對初心者的教學好重要,所以一直想著如果之後能夠寫些跟技術有關的東西,要寫寫教學文章。後來高中在社團當教學,寫過社課的教學講義,不過沒放上網路,至於主題是什麼就別問了。

就這樣,我對寫技術相關的文章或 blog,一直想以寫教學的角度撰寫。但是隨著年歲增長,學的東西越多,越發覺自己會的很少,要寫出好教學沒那麼簡單,小時候的豪氣干雲(?)也漸漸弱了下來。唉,還是小時候比較天不怕地不怕。

開這個 blog 以前,我早就有個放只有自己看得懂的筆記的 wiki,但總覺得只有自己看得懂的東西 po 出來像在丟人現眼,也就一直放著自己看。後來慢慢弄了這個 blog,還是很想寫教學(這到底是什麼制約…),但是越這樣想我寫出來的語法就越奇怪,跟寫論文的狀況有點像(咦?)。就像把書上看不懂在寫什麼鬼的文字,再轉成另一種我自己也還是看不懂的句子,這樣寫筆記的意義到底是……?Orz

到這幾天覺得其實沒必要那麼嚴肅,反正就是把從各種地方學來的知識技術,消化吸收後再用自己的話寫出來就好了。

總之就是一個將知識與技術咀嚼消化後再吐出來(?)的過程。