2011年12月23日 星期五

快快樂樂 Makefile 入門教學

有感於身旁還是很多人對於 Makefile 感到陌生決定來發篇教學文...

要注意的是在本篇文章中所採用的詞彙與正式用語可能有點出入, 主要是為了方便理解


Makefile 對於入門者而言最重要的兩個功能是:
  1. 建置規則 : 該目標要如何完成?
  2. 檔案相依性 : 如果 xxx 更新的話, 代表我要跟著更新(或著是重新編譯)



情境模擬

在這邊先來討論為什麼我們需要 makefile 

假設我們現在有三個檔案 : a.c, b.c, c.c

編譯的時候當然可以很直接的

gcc a.c b.c c.c -o prog

但如果今天是有三十個檔案的話 :  a.c, b.c, c.c ... z.c, aa.c, bb.c, cc.c, dd.c

那麼編譯時便需要

gcc a.c, b.c, c.c ... z.c, aa.c, bb.c, cc.c, dd.c -o prog

當然如果對於一般 shell 有點熟悉的話會很直覺的想到可以

gcc *.c -o prog

大致上是沒有問題的

那幹麼要學 Makefile 阿?

隨著程式碼長大, 編譯時間開始慢慢的變長

從原本的按下去瞬間完成, 慢慢的成長到 30 秒甚至是一分鐘時

會發現按下編譯後, 會想要跑去逛個 ptt 或看個拍賣, 再去逛個奇毛新聞

然後每次編譯的時間就從原本的一瞬間變成了十幾分鐘甚至半小時(疑?

但回頭分析一下上面的兩個編譯方式
  1. gcc a.c, b.c, c.c ... z.c, aa.c, bb.c, cc.c, dd.c -o prog
  2. gcc *.c -o prog
會發現每次編譯都是重新編譯每個檔案


發生這種事的時候很直覺得就會想要分開編譯成很多 .o 檔

結果編譯的過程變成


gcc -o a.o -c a.c
gcc -o b.o -c b.c
gcc -o c.o -c c.c
...
gcc -o z.o -c z.c
gcc *.o -o prog


然後你想修改 a.c, 然後重新編譯

gcc -o a.o -c a.c
gcc *.o -o prog

接著測試過後你發現有 bug , 需要更改 b.c, 然後重新編譯

gcc -o b.o -c b.c
gcc *.o -o prog

...

結果省了編譯時間多了手工時間

雖然可以避免編譯時跑去逛網頁但也很浪費時間

如果看到這邊覺得這樣的過程可以讓自己覺得很忙很認真而不想改善開發流程的話

就直接按上一頁吧



套用 Makefile !

終於要進入正題使用 Makefile 了,

使用 Makefile 主要是避免不要要的時間浪費, 以及一直重新的打字

開始介紹前再度複習 Makefile 兩個基本功能:
  1. 建置規則 : 該目標要如何完成?
  2. 檔案相依性 : 如果 xxx 更新的話, 代表我要跟著更新(或著是重新編譯)
在這邊我們想要透過 Makefile 來建置程式

那要怎樣才能把剛才所作的事換成用 Makefile 作勒?

就開始一步一步分析並從頭開始寫, 我們的目標是要建置 prog 這隻程式

打開一個 Makefile 並且打入

prog:

接著打入 make 他會跟你抱怨, 不知道怎麼建立 prog

make: Nothing to be done for `prog'.

所以這邊要開始進入 Makefile 第一個重要的功能了, 如何加入建置規則?

方式是在該目標下一行先敲一下 tab 然後開始寫指令

要注意, 一定要是 tab 而不能是空白

prog:
    gcc a.o b.o c.o -o prog

在這邊為了簡化還是先退回只有三個檔案

然後按下 make

gcc a.o b.o c.o -o prog

喔喔喔喔喔喔喔喔 現在只打 4 個字就取代 23 個字了!

但目前其他三的檔案還是沒有建立規則, 大致受上一樣畫葫蘆

prog:
    gcc a.o b.o c.o -o prog

a.o:
    gcc a.c -o a.o -c

b.o:
    gcc b.c -o b.o -c

c.o:
    gcc c.c -o c.o -c

在按下 make 後你可能會發現, 他跟你說 prog 已經是最新版了

make: `prog' is up to date.

現在就來修改 a.c 檔案的內容後重新按 make

...

你會發現

他還是跟你說 prog 已經是最新版了

make: `prog' is up to date.

主要原因是我們沒有告訴 make 檔案相依性

他不知道什麼時候該重新建置目標

要加入檔案相依性只要在目標後面加入所相依的檔案名稱即可

prog: a.o b.o c.o 
    gcc a.o b.o c.o -o prog

a.o: a.c 
    gcc a.c -o a.o -c

b.o: b.c 
    gcc b.c -o b.o -c

c.o: c.c 
    gcc c.c -o c.o -c


加入完相依性後再按 make 他就會重新編譯 a.o 跟 prog 了!


gcc a.c -o a.o -c
gcc a.o b.o c.o -o prog



所以用 Makefile 有啥好處 ?

從上面的例子可以看出使用 Makefile 來建置程式的好處 :

  1. 節省打編譯指令的時間
  2. 建立相依性後, 可自動偵測是否需重建
其實在現代的多核心機器下還可以利用 Makefile 的內建功能來達到平行建置

只要你在建置時在 make 後面加入 -jn 其中 n 帶入數字,

例如

make -j4

代表一次平行執行四個任務, 也就是最多同時會編譯四個檔案!

這個功能則是一般手動編譯較難以達成的, 並且可以非常有效的節省編譯時間



Makefile 常見錯誤訊息

一般而言寫 Makefile 最容易看到下列的錯誤訊息

Makefile:2: *** missing separator.  Stop.

但看到後請不要慌張, 這個訊息通常是你的建置規則前面沒有用 tab 所造成的

尋著他後面提示的行號去檢查很快就可以解決



雜談

Makefile 在 linux 環境及泛 Unix 家族底下都是常用的建置工具

但是事實上 Makefile 可以作的更多, 不只是編譯建置程式

在很多情況下我們可以利用 Makefile 的平行建置功能來達到平行執行某些工具

例如如果要跑 benchmark 之類的, 也給他寫成 Makefile

然後在 i7 上面 make -j8 就整個很開心

2011年12月14日 星期三

Android prelink in ICS

在 Android ICS 中已經將 apriori 及 soslim 都丟掉
言下之意就是 prelink 在 Android 中不再出現

根據 Commit 裡面的機制就是說硬體已經進步
所以不缺那點時間跟省下來的空間XD...

而且不是 gcc 那串 toolchain 的東西, 要分開維護很麻煩

並且對於 Address-Space-Layout Randomization 的有效性大大降低

2011年11月30日 星期三

libelf for arm

對於 arm 的 object file 使用 elfutils-libelf 時

取用 Symbol 需要注意 Symbol address 的部份

在 ELF for the ARM®  Architecture 的第 4.6.3 的部份有提到

Symbol Values 
  • In addition to the normal rules for symbol values the following rules shall also apply to symbols of type STT_FUNC: If the symbol addresses an ARM instruction, its value is the address of the instruction (in a relocatable object, the offset of the instruction from the start of the section containing it). 
  • If the symbol addresses a Thumb instruction, its value is the address of the instruction with bit zero set (in a relocatable object, the section offset with bit zero set). 
  • For the purposes of relocation the value used shall be the address of the instruction (st_value & ~1).
ARM 跟 thumb 的 Symbol 區別方式是看 Symbol Table 中 Symbol Address 的最低位元來判斷, 所以取用請記得 st_value & ~1, 免得取到的 Address 差一.

2011年3月30日 星期三

cuda-memcheck : CUDA Debug 好幫手

在撰寫 CUDA 程式的時候最煩惱的就是 Debug 困難

即使現在有了 cuda-gdb 也是相當的困難

而在 CUDA toolkit 裡面有附贈一隻叫 cuda-memcheck 的小程式

可以幫助你檢查程式中有無下列兩種錯誤:


1. Out of boundary memory access
2. Misalignment

那接下來先了解一下上面兩個錯誤是啥

第一個錯誤 Out of boundary memory access 很好理解

比如說你的陣列長度只有 10 但是你卻存取到了 10 以上

int a[10];
a[11] = 1111; // Out of boundary memory access !!

這件事情在傳統 C 語言上面很好察覺,

但在 CUDA 中通常 index 都是跟 threadIdx 以及 blockIdx 加起來之類的

一不小心就會寫錯

而第二個錯誤 Misalignment 則是一般在 x86 PC 上寫程式幾乎不會遇到的事情

原因在於 x86 硬體允許你作這件事, 在嵌入式系統的世界比較容易遇到這種事

alignment 這件事簡單來講就是記憶體存取時位址必須為 x 的倍數

例如 4 - byte alignment 的話, 位址必須能被 4 整除

合法的 4 - byte alignment  位址如 : 0x00ffff, 0x0, 0x8 等等
不合法的 4 - byte alignment  位址如 : 0x01, 0xff06, 0xa 等等

但通常這種情只會發生在你有對指標作些特殊處理時才會發生

例如

char p[10];
int *a = (int*)p+1;

之類的運算, 通常在寫影像處理相關的程式時
比較有可能寫出這類的程式碼
在寫 CUDA 時請盡量避開

在了解 cuda-memchech 的功能後來看看如何使用

假設你有一個 CUDA 程式 a.out

那你只要下

cuda-memcheck a.out

就可以開始檢查

如果有錯誤的話會吐出類似下面的訊息


========= CUDA-MEMCHECK
========= Invalid __global__ read of size 4
=========     at 0x00000038 in f
=========     by thread (0,0,0) in block (0,0)
=========     Address 0xfb00000228 is out of bounds
=========
========= ERROR SUMMARY: 1 error


在這個時候你會發現雖然他告訴你 out of bounds
但是根本不知道在那一行出錯阿!!!

這時候你重新編譯你的程式

nvcc -G oob.cu

加個 -G 然後

cuda-memcheck a.out

重新檢查


========= CUDA-MEMCHECK
========= Invalid __global__ read of size 4
=========     at 0x000000b0 in oob.cu:3:f
=========     by thread (0,0,0) in block (0,0)
=========     Address 0xfb00000228 is out of bounds
=========
========= ERROR SUMMARY: 1 error

就會報告錯誤的行數了!

另外有個 --continue flag

cuda-memcheck --continue ./a.out

這時候他檢測到錯誤的時候就會試著繼續跑跑看了

Misalignment 錯誤訊息差不多也是這樣就不浪費版面了

2010年11月16日 星期二

Add new instruction to SimpleScalar (1) - Modify Simple Scalar

這份教學主要包含三個部份

1. 加指令到 SimpleScalar  (實驗中採用該版本 http://www4.ncsu.edu/~hhashem/mase-alphalinux.htm)
2. 加指令到 gas (Binutils 2.20)
3. 加指令到 gcc (GCC 4.4.3)

不過著重在前兩項, 第三項沒背景知識一時半刻也弄不來

教學範例是為 Alpha 加上整數除法!

(Alpha真他媽的畸形 有浮點數除法沒整數除法)


為你的SimpleScalar增加新的指令!

雖然這鬼東西已經夠老了, 不過可怕的是中文資源幾乎都只教怎麼安裝

沒教怎麼改

而增加指令只有首頁那該死的 def file format 可以參考

去年面對的時候選擇了逃避

沒想到其實沒有想像中的難...

就進入正題吧

http://www.simplescalar.com/docs/README-def.txt

有空還是請記得讀一下官方文件



1. 加指令到 SimpleScalar

為了達到增加指令的裡面,我們必須要先了解 SimpleScalar 是怎麼 decode 的

首先 SimpleScalar 採用一種名為 Decode Tree 的鬼東西,大致上來說就是需要在新增指令的時候大概按照他的樹狀結構編排

Decode Tree 的開端來自於 MD_TOP_OP(INST) 這個 Macro


這個是來自 ARM 的

#define MD_TOP_OP(INST) (((INST) >> 24) & 0x0f)

ARM 使用 23~27 bit 當主要的 Opcode
28~31被拿去當 Condition Code

這個是來自 Alpha 的

#define MD_TOP_OP(INST)         (((INST) >> 26) & 0x3f)

Alpha 則是前 7 bit 都抓去當 Opcode

所以有了 MD_TOP_OP 後我們可以開始建構 Decode Tree 了

以Alpha 為例

LDA 的 OPCODE 是 0x08

那麼我們就直接定義


DEFINST(LDA,                    0x08,
        "lda",                  "a,o(b)",
        IntALU,                 F_ICOMP,
        DGPR(RA), DNA,          DNA, DGPR(RB), DNA)

第一個參數是拿來辨別的
第二個參數是OPCODE
第三個參數是拿來 disasm 用的資訊
第四個參數也是拿來 disasm 用的資訊
第五個參數是會用到哪個 Functional Unit, 這個在 sim-outorder 下會有影響
第六個參數則是分類該指令, 同樣的也是在 sim-outorder 下會有影響
接下來六七個是 Output Register , 拿來控制 Dependency 用的
八九十則是 Input Register , 也是拿來控制 Dependency 用的
所以後五個參數, 請直接跟同類型的指令拷貝

注意一下 ARM 的參數個數有點不太一樣, 主要差在後面不止五個

接著請記得在這個 Marco 的前面定義一個

LDA_IMPL 的 Macro


#define LDA_IMPL                    \
{                                   \
  SET_GPR(RA, GPR(RB) + SEXT(OFS)); \
}

這個是用來實作該指令的功能的


看到這邊應該正常來講已經可以做到基本的增加指令了

但是通常事情沒那麼簡單

常常有 OPCODE 外還會有 Function Code , Alpha 中Operate Instruction 就是這樣, 在 MIPS 中也有

那麼我們就不能直接像 LDA 般那麼歡樂的加指令

這時候套個 SimpleScalar 說明文件的術語

我們需要 Sub Decode Tree !

意思是說我們用 OPCODE 分了某類後想要再依照某些欄位分類的話就要 Sub Decode Tree

阿 Sub Decode Tree 到底是什麼 ?

簡單的說就是類似再一次的設定 MD_TOP_OP 的那種感覺

不過已經不是 TOP 而是 SECOND 了, 喔 這不是重點

接下來我們需要 DEFLINK 這個東西來定義 Sub Decode Tree Root

例如以 Alpha add, sub 等等的 OPCODE 0x10 為例



我們只要這樣定義


DEFLINK(INTA, 0x10, "inta", 5, 0x7f)

第一個欄位是該  Sub Decode Tree 的名稱
第二個欄位是OPCODE
第三個欄位是註解
第四個欄位是新的OPCODE的偏移量
第五個欄位是新的OPCODE的MASK

所以這個 DEFLINK 的效用大概會等於

MD_SECOND_OP(INST) ((INST >> 5) & 0x7f)

喔, 不過沒有 MD_SECOND_OP 這麼 Macro , 別真的去宣告


就會出現一顆 Sub Decode Tree 了


接下來很重要的是我們需要 CONNECT 兩棵 Decode Tree

CONNECT(INTA)

然後就只要符合 MD_TOP_OP(INST) = 0x10 的都會掉進這顆叫 INTA 的 Sub Decode Tree 了~

接著我們只要開心的 DEFINST 就可以了~

不過其實很多情況下我們並不只想要兩層 Decode Tree 就解決, 有時候可能會需要三層甚至四層, 這時候該怎麼辦呢?

方法就是在該 Sub Decode Tree 的 CONNECT 後再 DEFLINK

那麼這個 DEFLINK 就會是 Sub Sub Decode Tree 了

在 Alpha 中就用這種無聊的技巧來分別第三個 Operand 是 Imm 還是 Reg

對, 真的很無聊...明明就用個 if 就解決了, 還要靠這該死的 Decode Tree

如果無聊去看實作的話會發現這樣做只是比較慢而且免費讓 DEF 膨脹到快兩倍而已






下集待續吧~

2010年11月7日 星期日

Qt Creator for CentOS 5.x

用 CentOS 最 g8 的就是一堆比較新的套件都不太能用

要碼自己編不然就放棄
(而且還會遇到一堆套件版本相依性問題!

最近要用到 qt creator 簡直是麻煩到爆炸了

還好有個好心的老外有提供

http://joseph.freivald.com/linux/


安裝方法

1.

rpm -ivh http://software.freivald.com/centos/software.freivald.com-1.0.0-1.noarch.rpm
2.
然後就可以開心的用 yum 安裝了
yum install qt-creator
歐~人生真是美好~

DebugStream for C++

千年發文

其實這是剛才心血來潮寫的小 helper class

主要用途是來控制輸出資訊用的

並且可以在輸出時指定輸出等級, 例如 lv 3 以上才輸出之類的

廢話不多說先來段使用範例

#include "DebugStream.hpp" 
// 使用前只要 include header 就可以了
#include <iomanip>  
// iomanip for std::setprecision
int main(){
  DebugStream ds(10);       
  // 開一個 DebugStream, debug level 設為 10
  ds[3] << "abc\n";   
  // verbose level 3, 小於debug level 所以會被印出來
  ds[11] << "XD\n";   
  // verbose level 11, 大於debug level 所以不會被印出來
  ds[8] << 123 << std::endl;
  ds.precision(2);          
  // 跟平常使用 cout 習慣一樣喔~
  ds[5] << 0.54321 << std::endl; 
  ds[5] << std::setprecision(4) << 0.12354 << std::endl; 
  // 當然也可以採用這種方式~
  return 0;
}

預計未來可能應該大概會加入 printf 的功能來達到 ds[3].printf("%d", 3); 之類的功能

code 按這裡取得完整程式碼