My code works, I don’t know why.

國王的耳朵是驢耳朵

再談 Git Remote

| Comments

  • 更新:
    • Apr/09/2016:感謝網友柏瑀的告知,修正白字。

git remote 是一個常常被忽略的東西,這次就來看看這個指令和對應的觀念吧。

一樣,先講測試環境,避免無法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.3 LTS
Release:  14.04
Codename: trusty

$ git --version
git version 2.7.0

本次介紹目錄如下

背景說明

先來問問男人git remote是什麼吧?

1
2
3
4
GIT-REMOTE(1)                                     Git Manual                                    GIT-REMOTE(1)

NAME
       git-remote - Manage set of tracked repositories

白話來說,git remote是用來管理多個tracked repositories。(中文難翻,意思是你要追蹤的git repository)。在大部分的情況,git clone下來後只需要和原本clone的遠端互動的話,你只有一個remote,那麼你機乎不會感受到remote的運作,我天生駑鈍,剛開始只是覺得奇怪怎麼有時候後會有個origin跑出來而已。

如果夠幸運(還是不幸?)你不太會需要遇到需要處理remote的情況。不過在github上面,倒是蠻有機會要用到remote,情境如下。

  • 你在github上面有帳號,要fork別人的project A,fork來的稱為A+
  • 你在A+開發,同時A也在開發
  • A+有需要和A同步,也許是A有新功能,或是你要回饋程式碼總不能回饋和A差異太大的程式碼

A+A同步,是remote常用的情境。常用到github直接給懶人包。大概描述如下:

  • A+加一個remote,URL為A的URL。白話來說,就是A+要track repository A
    • remote必須給個名字,github的懶人包上給的名字叫upstream。
  • 既然加了一個tracked repository,你就可以做
    • 拉remote程式碼下來
    • 切換到remote下面的branch
    • merge remote 的branch

git remote指令介紹

在介紹指令之前,先建立示範git repository 以便下面的說明。情境如下

  1. 建立新的git repository,稱為repo_1
  2. clone repo_1repo_2
  3. clone repo_2repo_3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ git init repo_1
Initialized empty Git repository in /tmp/test_remote/repo_1/.git/

$ cd repo_1/
/repo_1$ touch test_file && git add test_file && git commit -a -m "init for test"
[master (root-commit) 6847c3c] init for test
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 test_file

$ cd ../

$ git clone --bare repo_1 repo_2
Cloning into bare repository 'repo_2'...
done.

$ git clone repo_2/ repo_3
Cloning into 'repo_3'...
done.

關於repo_2的–bare部份,一言難盡。長話短說,不這樣幹repo_3不能push到repo_2,有興趣可以看這邊

如果你的git repository是clone過來的,一定會有一個預設remote。沒意外的會叫origin (什麼?你的remote叫aosp? 呃太複雜不討論),接下來你可以對於remote 操作,分別說明如下:

查詢remote資訊

  • git remote
    • 顯示remote名稱
  • git remote -v
    • 顯示remote名稱及remote URL
  • git remote show remote名稱
    • 顯示remote名稱的詳細資訊

直接看範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ cd /tmp/test_remote/repo_3

$ git remote
origin

$ git remote -v
origin    /tmp/test_remote/repo_2/ (fetch)
origin    /tmp/test_remote/repo_2/ (push)

$ git remote show origin
* remote origin
  Fetch URL: /tmp/test_remote/repo_2/
  Push  URL: /tmp/test_remote/repo_2/
  HEAD branch: master
  Remote branch:
    master tracked
  Local branch configured for 'git pull':
    master merges with remote master
  Local ref configured for 'git push':
    master pushes to master (up to date)

新增remote

  • git add remote名稱 remote_repository_URL

範例如下

1
2
3
4
5
6
7
$ git remote add upstream /tmp/test_remote/repo_1/

$ git remote -v
origin    /tmp/test_remote/repo_2/ (fetch)
origin    /tmp/test_remote/repo_2/ (push)
upstream  /tmp/test_remote/repo_1/ (fetch)
upstream  /tmp/test_remote/repo_1/ (push)

新增完畢後,請記得remote_名稱remote_名稱 remote_branch_名稱remote_名稱/remote_branch_名稱是你要操作remote的參數。

另外你還可以加入多個remote來場大亂鬥。例如你從A fork A+,也許有人fork 出來的A++有你想要的功能,就可以再把A++加入你A repository的remote。

拉remote repository

  • git fetch remote_名稱
  • git pull remote_名稱/remote_branch_名稱

我們先在repo_1開一個branch稱為br1

1
2
3
4
5
6
7
8
9
$ cd /tmp/test_remote/repo_1/

$ git checkout -b br1
Switched to a new branch 'br1'

$ touch test_on_branch && git add test_on_branch && git commit test_on_branch -m "test"
[br1 cc7222f] test
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 test_on_branch

接下來就是範例時間

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
$ git fetch upstream
From /tmp/test_remote/repo_1
 * [new branch]      br1        -> upstream/br1
 * [new branch]      master     -> upstream/master

$ ls
test_file

$ git pull upstream br1
From /tmp/test_remote/repo_1
 * branch            br1        -> FETCH_HEAD
Updating 6847c3c..cc7222f
Fast-forward
 test_on_branch | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 test_on_branch

$ ls
test_file  test_on_branch

$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)
nothing to commit, working directory clean

注意最後的指令,因為我們pull了upstream/br1,所以local多了一個commit。因此訊息中有說你目前repository 比origin/master(你clone的remote/branch)多一個commit。你可以push回origin/master

remote的branch操作

原則就是命令中有參數會是remote_名稱remote_名稱 remote_branch_名稱remote_名稱/remote_branch_名稱

  • 切換
1
2
3
4
5
6
7
8
9
10
11
12
13
$ git checkout upstream/br1
Note: checking out 'upstream/br1'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b <new-branch-name>

HEAD is now at bd026e3... test

中間的一堆英文簡單來說是因為git checkout沒特別參數,git只是把HEAD指到命令中的hash而已。如果你要同時在local開一個branch,可以這樣幹:

1
2
3
$ git checkout --track  upstream/br1
Branch br1 set up to track remote branch br1 from upstream.
Switched to a new branch 'br1'
  • merge
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
# 先在upstream/br1建立新的commit
$ cd /tmp/test_remote/repo_1/

$ touch test1_on_branch && git add test1_on_branch && git commit test1_on_branch -m "test1"
[br1 d82612a] test1
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 test1_on_branch

# 切回repo_3
cd /tmp/test_remote/repo_3

# 先fetch
$ git fetch upstream
remote: Counting objects: 2, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 2 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (2/2), done.
From /tmp/test_remote/repo_1
   bd026e3..d82612a  br1        -> upstream/br1

# 再merge,事實上和git pull upstream br1 一樣效果(默)
$ git merge upstream/br1
Updating bd026e3..d82612a
Fast-forward
 test1_on_branch | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 test1_on_branch

刪除remote

  • git remote remove remote名稱

範例如下

1
2
3
4
5
$ git remote remove upstream

$ git remote -v
origin    /tmp/test_remote/repo_2 (fetch)
origin    /tmp/test_remote/repo_2 (push)

關於GNU Inline Assembly

| Comments

以前稍微接觸過GNU Inline Assembly,對於那些奇怪的符號總是覺得匪夷所思。這次找時間把他整理一下。雖然釐清了一些觀念,不過卻產生更多的疑惑,也許以後有機會看到範例會慢慢有感覺吧。

目錄

前言

我自己對於GNU Inline Assembly的看法。

  • 編譯器 夠聰明,所以暫存器分配可以安心交給編譯器處理。也就是說語法上面要處理這塊。
  • 暫存器、變數有些資訊仍然要讓編譯器知道,讓編譯器產生object binary遵守這樣的規則,如
    • 這個operand是一個暫存器
    • 這個operand是一塊記憶體
    • 這個operand是浮點常數
  • 不想讓編譯器幫你安排暫存器,而是在Inline Assembly指定暫存器的話,就要明確的列出來。讓編譯器知道這些暫存器有被改過資料,進而針對這些暫存器做適當的處理。

測試環境

我使用ARMv7為主的Banana Pi開發版加上Lubuntu 14.04作為測試環境。

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

$ dmesg
...
[    0.000000] Linux version 3.4.90 (bananapi@lemaker) (gcc version 4.6.3 (Ubuntu/Linaro 4.6.3-1ubuntu5) ) #2 SMP PREEMPT Tue Aug 5 14:11:40 CST 2014
[    0.000000] CPU: ARMv7 Processor [410fc074] revision 4 (ARMv7), cr=10c5387d
...

$ gcc -v
...
Target: arm-linux-gnueabihf
...
gcc version 4.8.4 (Ubuntu/Linaro 4.8.4-2ubuntu1~14.04) 

語法

inline assembler關鍵字是asm,不過__asm__也可以使用()。

根據目前(Dec/2015)的gcc手冊,inline assembler有分為basicextended兩種。雖然我使用的平台是gcc 4.8.4,而且gcc 4.8.5手冊(官方網站上沒有4.8.4手冊)並沒有提到這個部份。但是目前語法上測試的確沒有問題,但是有些說明上面卻很難驗證是否可以套用到4.8.5上(例如最佳化的說明、需要注意常犯的錯誤),請自行斟酌。

以下是整理自最新的手冊說明,請自行斟酌您使用的gcc版本是否有符合。

Basic inline assembler

1
 [ volatile ] asm("Assembler Template");

以下是整理自最新(Dec/2015)的手冊說明節錄,請自行斟酌您使用的gcc版本是否有符合。

  • basic inline assembler 預設就是volatile
  • 基本上編譯器只是把引號內的東西抄錄,所以只要組譯器支援的語法,就可以寫入Assembler Template內
  • 和extended inline assembler的差異
    • extended inline assembler 只允許在函數內使用
    • naked屬性的函數必須使用basic inline assembler(見註解)
    • basic inline assembler就是把template內的字串作為組合語言組譯。而%字元在extended inline assembler有特別意義,然而有些組合語言如x86中%是暫存器語法的一部份。以至於%字元要在extended inline assembler中改為%%才是真正的意思,舉個例子%eax->%%eax
  • 有要使用C 語言的資料,使用extended inline assembler比較妥當
  • GCC 最佳化時是有可能把你的inline assembler幹掉或是和你想的不一樣,請注意
  • 你不可以從一個asm(..)裏面跳到另外一個asm(..)的label

最簡單的廢話範例如下

1
asm("nop"); /* 啥事都不要做 */

在沒有使用C 語言的變數下,就和一般的組合語言沒有差太多。 更複雜一點的例子可以看rtenv裏面的使用方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
size_t strlen(const char *s) __attribute__ ((naked));
size_t strlen(const char *s)
{
    asm(
        "    sub  r3, r0, #1            \n"
        "strlen_loop:               \n"
        "    ldrb r2, [r3, #1]!        \n"
        "    cmp  r2, #0                \n"
        "   bne  strlen_loop        \n"
        "    sub  r0, r3, r0            \n"
        "    bx   lr                    \n"
        :::
    );
}

要注意__attribute__ ((naked));是有意義的。這是為何這段範例沒有直接指名用到C 語言函式變數名稱的關鍵點。有興趣請看這邊,請直接找字串naked

Extended inline assembler

1
2
3
4
5
 asm [volatile] ( AssemblerTemplate
                    : OutputOperands   // optional
                  [ : InputOperands    // optional
                  [ : Clobbers ]       // optional
                  ])

Assembler Template基本上就是你要寫的組語加上 Inline Assembler 專用的符號。要注意的是,在編譯的過程中,你寫的inline assembler可能由於最佳化考慮不會被組譯。如果你確認你inline assembler一定要被組譯,請加上volatile keyword。

Assembler 專用的符號節錄如下:

符號 說明
%% 單一%字元
%{ 單一{字元
%} 單一}字元
|{ 單一|字元
%= 只知道並驗證過會產生唯一的數字。用途部份看不懂,英文真是奧妙的東西啊。

AssemblerTemplate

由於前言提到的三項個人猜測,造成inline assembler要使用C 語言變數時語法會出現很多令人眼花撩亂的符號。

由於編譯器提供協助分配暫存器和記憶體,也就是說需要有對應的語法指定目前指令的operand是什麼。GCC 有兩種方式指定,分別是

  • 編號指定,從零開始編號
  • Symbolic name指定: GCC 3.1以後支援出處

分別給個範例讓各位感受一下

編號指定,從零開始編號

這邊%0, %1就是編號。後面operand可以看到就是指定變數、以及變數的限制。這邊簡單解釋一下=表示這是一個輸出、而r表示變數要放在暫存器中、m表示變數是放在記憶體中。有興趣比對編譯出來的binary反組譯時的組合語言請看這邊。不過編號和指令中的operand似乎很隨意,我沒有看到特殊規範。只能交叉比對assembler template和input/output operands才能看出端倪。我猜更複雜的情況你還要比對反組譯出來的結果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>

int main(void)
{
    int var1 = 12;
    int var2 = 10;

    asm("mov %0, %1 \n  \
         add %1, %0, $1" : "=r"(var1), "=r"(var2) : "r"(var2), "r"(var1):);
    printf("var1 = %d, var2 = %d\n", var1, var2);

    asm("ldr r5, %0 \n":           : "m"(var1): "r5");
    asm("str r4, %0"   : "=m"(var2):          : "r4");
    return 0;
}

Symbolic name指定

編號的缺點就是可讀性比較差,所以gcc 3.1出現使用symbolic name的方式。至於那一個比較好,看你自己習慣。

直接把上面的範例更改一下。GCC 4.8.5手冊上面說symbolic name隨便取,甚至和變數同名稱都可以,只要單一asm(…)內的 symbolic name不要重複就好。有興趣比對編譯出來的binary反組譯時的組合語言請看這邊

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>

int main(void)
{
    int var1 = 12;
    int var2 = 10;

    asm("mov %[my_var1], %[my_var2] \n  \
         add %[my_var3], %[my_var4], $1" :
            [my_var1] "=r" (var1), [my_var3] "=r" (var2) :
            [my_var2] "r"  (var2), [my_var4] "r"  (var1) :);
    printf("var1 = %d, var2 = %d\n", var1, var2);

    asm("ldr r5, %[my_var1] \n":: [my_var1] "m"(var1): "r5");
    asm("str r4, %[my_var1]": [my_var1] "=m" (var2):: "r4");
    return 0;
}

接下來來看每個欄位吧。

Output operands

1
[ [asmSymbolicName] ] constraint (cvariablename)

[asmSymbolicName] 是GCC 3.1以後支援語法,如前所述,不用Symbolic Name就用編號方式對應assembler template operand。

指定結果要存在C 語言中的那個變數。要注意的除了要設定對的資訊(constraints,下面會節錄) 以外,operand的prefix一定要是=+這兩個constraint。

隨便舉幾個範例

  • =r(var1):變數請寫入並放在暫存器中
  • =m(var1):變數請寫入並存到記憶體中

Input operands

1
[ [asmSymbolicName] ] constraint (cvariablename)

[asmSymbolicName] 是GCC 3.1以後支援語法,如前所述,不用Symbolic Name就用編號方式對應assembler template operand。

指定要從在C 語言中的那個變數取出資料。主要是要設定對的資訊(constraints,下面會節錄) 。

  • r(var1):變數請放在暫存器中
  • m(var1):變數是在記憶體中

Clobbered registers list

先講結論,在asm("語法")中明確地指定暫存器名稱的話,要在這邊列出。

現在我會習慣查單字。Clobbered查英文單字會發現就是把東西用力地砸毀。所以翻譯成中文就是「砸爛的暫存器列表」。什麼是爛掉的暫存器?就是本節前面的結論囉。

另外從Dec/2015的gcc 手冊還有找到下面語法,一樣請注意版本問題

符號 說明
“cc” 和狀態有關的flag暫存器會被修改
“memory” 這段組合語言會讀寫列出operand以外的記憶體內容,因此編譯器會視情況備份暫存器或讀寫記憶體

Constraints

1
2
3
4
<Constraints>       ::= <Constraint Modifier> <Other Constraints> | <Other Constraints>
<Other Constraints> ::= <Simple Constraints> | <Machine Constraints>

; /* 以上BNF是我整理的,terminal symbol請自行看手冊 */

節錄整理我看得懂感興趣的部份。

Simple Constraints
符號 說明
空白字元 會被忽略,排版用
m operand 存放在記憶體中
r operand 將被放在暫存器中
i operand 是一個整數常數,該常數包含下面的情形(symbolic name):`#define MAX_LINE (32)`
n operand 是一個整數常數,只允許填入數字
E operand 是一個浮點數常數,不清楚和`F`的差異
F operand 是一個浮點數常數,不清楚和`E`的差異
g operand 存在暫存器(r)或是記憶體內(m),或是這是一個整數常數
X 不用檢查operand

  

你可以使用組合技如"rim",如果這樣寫的話,意思是要編譯器幫你挑一個最適合的方式處理對應於assembler template內的operand。   

Constraint Modifier
符號 說明
= 表示這是一個write only的operand,必須為contraint開始字元。
+ 表示這個 operand 在指令中是同時被讀寫的,必須為contraint開始字元。
& 該operand 為earlyclobber。earlyclobber就是在instruction讀取該operand前,該operand會被寫入。雖然如此,到底是多久前?是和data hazard有關嘛?還是跟資料一致性有關?或者是和編譯器 最佳化造成非預期結果有關?真是一團謎完全搞不懂做啥用,也不清楚使用時機。這邊有範例,一樣搞不懂為什麼要有+, &的modifier
% 該operand 可以讓編譯器 決定這個operand是否和後面的operand交換(commutative),完全搞不懂做啥用

  

ARM 專用的Constraint

我參考的是gcc 4.8.5手冊(因為和測試環境的gcc版本最接近),可能有版本的問題,這些我都沒有做實驗測試,請自行斟酌。

符號 說明(一般模式)
w VFP 浮點運算
G 浮點運算的0.0
I 8 bit正整數
K I contraint 的invert (一的補數),Wen: 不知道為什麼要扯到I constraint?
L I contraint 的負數 (二的補數),Wen: 不知道為什麼要扯到I constraint?
M 0 ~ 32的正整數
Q 要參考的記憶體位址存放在一個暫存器內
R operand是一個const pool內的東西,不要問我const pool是啥,估狗到都和Java有關
S operand 目前檔案中.text內的一個symbol
Uv VFP load/store 指令可存取的記憶體
Uy iWMMXt load/store 指令可存取的記憶體
Uq ARMv4 ldrsb 指令可存取的記憶體

完整列表在這邊,要注意的是2015年12月的手冊又多了一些新的contstraint。請自行參考。

參考資料

附錄

  • C 語言標準有提到編譯器可以使用asm keyword,而且沒有定義語法。有興趣可以找C11C99C89的標準,直接搜尋asm就可以看到了。

  • naked使用basic inline assembler和extended inline assembler比較

下面兩個函數,strcmp1沒有任何extended inline assembler而strcmp2硬塞了一個下去:

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
int strcmp1(const char *a, const char *b) __attribute__ ((naked));
int strcmp1(const char *a, const char *b)
{
    asm(
        "strcmp_lop1:                \n"
        "   ldrb    r2, [r0],#1     \n"
        "   ldrb    r3, [r1],#1     \n"
        "   cmp     r2, #1          \n"
        "   it      hi              \n"
        "   cmphi   r2, r3          \n"
        "   beq     strcmp_lop1      \n"
        "    sub     r0, r2, r3      \n"
        "   bx      lr              \n"
        :::
    );
}

int strcmp2(const char *a, const char *b) __attribute__ ((naked));
int strcmp2(const char *a, const char *b)
{
        int i;
    asm(
        "strcmp_lop2:                \n"
        "   ldrb    r2, [r0],#1     \n"
        "   ldrb    r3, [r1],#1     \n"
        "   cmp     r2, #1          \n"
        "   it      hi              \n"
        "   cmphi   r2, r3          \n"
        "   mov     %1, $1 \n"
        "   beq     strcmp_lop2      \n"
        "    sub     r0, r2, r3      \n"
        "   bx      lr              \n"
        :"=r"(i)::
    );
}

我們可以比較一下下面兩個函數最後編譯出來的指令,strcmp2顯然和我們預期的差很多。

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
000083f4 <strcmp1>:


int strcmp1(const char *a, const char *b) __attribute__ ((naked));
int strcmp1(const char *a, const char *b)
{
    asm(
    83f4:  f810 2b01     ldrb.w r2, [r0], #1
    83f8:  f811 3b01     ldrb.w r3, [r1], #1
    83fc:  2a01         cmp  r2, #1
    83fe:  bf88         it   hi
    8400:  429a         cmphi    r2, r3
    8402:  d0f7         beq.n  83f4 <strcmp1>
    8404:  eba2 0003    sub.w  r0, r2, r3
    8408:  4770        bx   lr
        "   beq     strcmp_lop1      \n"
        "    sub     r0, r2, r3      \n"
        "   bx      lr              \n"
        :::
    );
}
    840a:  4618        mov  r0, r3

0000840c <strcmp2>:
        "   beq     strcmp_lop2      \n"
        "    sub     r0, r2, r3      \n"
        "   bx      lr              \n"
        :"=r"(i)::
    );
}

  • 範例一的反組譯節錄
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
$ objdump -d -S asm
...
000083f4 <main>:
#include <stdio.h>

int main(void)
{
...
    int var1 = 12;
    83fa:  230c         movs r3, #12
    83fc:  603b         str  r3, [r7, #0]

    int var2 = 10;
    83fe:  230a         movs r3, #10
    8400:  607b         str  r3, [r7, #4]

    asm("mov %0, %1 \n  \
         add %1, %0, $1" : "=r"(var1), "=r"(var2) : "r"(var2), "r"(var1):);
    8402:  687b         ldr  r3, [r7, #4]
    8404:  683a         ldr  r2, [r7, #0]
    8406:  461a         mov  r2, r3
    8408:  f102 0301    add.w  r3, r2, #1
    840c:  603a         str  r2, [r7, #0]
    840e:  607b         str  r3, [r7, #4]
...
    asm("ldr r5, %0 \n":           : "m"(var1): "r5");
    8424:  683d         ldr  r5, [r7, #0]

    asm("str r4, %0"   : "=m"(var2):          : "r4");
    8426:  607c         str  r4, [r7, #4]
...
}
...

  • 範例二的反組譯節錄
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
$ objdump -d -S asm
...
000083f4 <main>:
#include <stdio.h>

int main(void)
{
...
    int var1 = 12;
    83fa:  230c         movs r3, #12
    83fc:  603b         str  r3, [r7, #0]

    int var2 = 10;
    83fe:  230a         movs r3, #10
    8400:  607b         str  r3, [r7, #4]

    asm("mov %[my_var1], %[my_var2] \n  \
         add %[my_var3], %[my_var4], $1" :
            [my_var1] "=r" (var1), [my_var3] "=r" (var2) :
            [my_var2] "r"  (var2), [my_var4] "r"  (var1) :);
    8402:  687b         ldr  r3, [r7, #4]
    8404:  683a         ldr  r2, [r7, #0]
    8406:  461a         mov  r2, r3
    8408:  f102 0301    add.w  r3, r2, #1
    840c:  603a         str  r2, [r7, #0]
    840e:  607b         str  r3, [r7, #4]
...
    asm("ldr r5, %[my_var1] \n":: [my_var1] "m"(var1): "r5");
    8424:  683d         ldr  r5, [r7, #0]

    asm("str r4, %[my_var1]": [my_var1] "=m" (var2):: "r4");
    8426:  607c         str  r4, [r7, #4]
...
}

在Banana Pi設定WPA2-PSK無線網路

| Comments

Banana Pi是一套ARMv7為處理器的開發版。一般來說照官方網頁把IMAGE燒到SD卡,外接鍵盤、滑鼠、HDMI螢幕,再通電即可透過GUI設定網路。

由於手上沒有任何外接設備,只有USB轉RS232線和USB WiFi。因此我只能在這樣的設備上設定網路,設定完成後就可以透過ssh server從外面連進去版子了。

照例先描述環境

HOST端

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

$ minicom --version
minicom version 2.7 (compiled Jan  1 2014)
...

設備端,假設你已經將系統燒入到SD卡中

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

接上TTY

首先你要有一條RS232轉USB的設備,上頭有TX/RX/GND/VCC,VCC在這邊用不到。

版子上面其實已經幫你把腳位標示好了,上面有TX/RX和GND如下:

剩下的就是把線路接起來。由於版子已經裝上外框,我有用鑷子協助連接線路。

線路接完後,將USB接上你的HOST,檢查下面幾項

  • dmesg確認HOST找到/dev/ttyUSBn(n為0開始的正整數)
  • 確認你的終端機(我用minicom)設備指定/dev/ttyUSBn(n為0開始的正整數)
  • 確認你的終端機(我用minicom)參數為115200 BPS,8N1,軟體硬體流量控制關閉

開啟你的終端機軟體,然後版子通電。當終端機畫面進入提示符號,請輸入帳號密碼。Banana Pi有預設的帳號密碼請自行上網查詢。

設定無線網路

Ubuntu 是透過/etc/network/interface去設定網路介面。這邊我們可以分成兩個部份討論

設定無線網路介面

首先你要下ifconfig -a看看你的無線網路介面名稱是什麼。我這邊是wlan2,為什麼不是wlan0,不要問我。

接下來就是修改/etc/network/interface,先貼上我網路參考的部份

/etc/network/interface
1
2
3
4
5
auto wlan2

allow-hotplug wlan2
iface wlan2 inet dhcp
wpa-conf /etc/wpa_supplicant/wpa_supplicant.conf

大概解釋一下

  • autoifup指令有-a參數會把有auto的網路介面全部bring up (bring up請自行估狗)
  • allow-hotplug:當kernel偵測到該網路介面被接上會自動bring up該網路介面,出處
  • iface wlan2 inet dhcp:指定網路介面wlan2使用TCP/IP,動態分配IP
  • wpa-conf /etc/wpa_supplicant/wpa_supplicant.conf:找不到最原始出處,網路上就算是Debian官方文件也直接拿來用而已,man -K wpa-conf也找不到。不過字面上不太難猜,就是指定wpa 會吃的config檔案路徑。

設定無線網路連線

前面有看到設定wpa的config檔案,接下來就來設定吧。我這邊是沒有該config檔,所以要自己新增一個。基本上就是設定SSID,密碼,加密方式,以及說明是否你要連的AP是否沒有broadcast SSID等。這邊我只是參考這邊,有興趣的人可以自行鑽研。

/etc/wpa_supplicant/wpa_supplicant.conf
1
2
3
4
5
6
7
8
9
10
11
12
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1

network={
ssid="AP的SSID"
scan_ssid=1 # 如果你的AP 是沒有broadcast SSID就要加這個
psk="你的AP 密碼(passphase)"
proto=RSN
key_mgmt=WPA-PSK
pairwise=CCMP
auth_alg=OPEN
}

設定完畢確認連線正常、有安裝sshd後,剩下就透過ssh操作版子了。祝好運!

空間母語 - 老街立面工法篇

| Comments

以下為課程筆記,我沒有專業能力判斷是否有誤解,請自行斟酌。

  • 街屋立面產生原因
    • 新建物
    • 街道拓寬舊的建築物被切掉
  • 人造石材質卷草工法
    • 開模
    • 泥塑(堆花)
  • 三角窗有一段時期流行將轉角切成三等份。範例如下:
  • 日治時代唯二允許的廟會 (待查證)
    • 北港朝天宮
    • 霞海城隍廟
  • 大溪老街和大稻埕以及葉金塗宅是同一批團隊建造的。
    • 陳旺來、陳三川兄弟
  • 由於日治時期和之前台灣石材缺乏,因此使用人造石頻率高於日本。
  • 人造石材料來源
    • 宜蘭石,天然碎石,圓潤無稜角
    • 大理石震碎
  • 葉宅新砌磚和舊的差別可從磚頭之間收縫粗細判斷
  • 以前的建築計費方式是按實際工作日計工錢,所以耗工時可以慢工出細活。然而現在是一份工程一筆費用,也就是說如果可以愈快完成,那麼就可以把資訊空出來接下一筆工程,因此慢工出細活反而是違反利潤的事情。
  • 角木(不知道是啥,似乎屋頂疊瓦和樓層都有用到)
  • 建築物裝修目的
    • 防災
    • 保護結構
    • 舒適度考量
    • 美觀
  • 建物裝修工法
    • 泥塑(堆花)下面兩種原料
      • 水泥
      • 灰泥(比較不耐久)
        • 白灰、稻殼混合
    • 剪黏
      • 使用灰泥以及鐵絲(後期使用不繡鋼線,銅線(銅線沒記是否後期))塑形,再使用瓦片黏上
      • 使用瓷片、後來也有人有嘗試使用壓克力和玻璃
      • 現代有量產版本,有外型成品如龍,匠師只要把瓷片黏上成品即完成
      • 早期瓷片是從真的碗取出,後來有單純剪黏用瓷碗。主要差別是剪黏用瓷碗不需要立起來,所以碗面和桌面接觸,確保力起來那個部份就不用做了
    • 交趾陶
    • 人造石
      • 做廟的團隊和做街屋的團隊可以是同一批
      • 立面山牆背面其實,你知道的
      • 洗石子
        • 將細石和水泥還有其他塗料混合後,塗上平面,待快乾時使用水沖洗平面,露出石頭部份
        • 工法
          • 平面
          • 泥塑
          • 開模
      • 斬石子
        • 將細石和水泥還有其他塗料混合後,塗上平面,待快乾時使用斬刀依工法塗抹平面,塑造不同石材的質感。
      • 磨石子

其他

  • 左官職人:日本泥水匠稱呼,日本的泥塑是由左官職人負責。台灣的剪黏師傅很多也包辦泥塑部份。
  • 丁蘭尺:陰宅使用
  • 門公尺:住家使用
  • 何金龍:佳里金唐殿
  • 蘇陽水:交趾陶
  • 洪坤福:大龍峒保安宮
  • 李重耀:維修過戰後總統府建築師
  • 中和圓通寺大象
  • 郭德蘭:泥塑匠師,郭三川之子
  • 國分直一
  • 桃園有圓頂加泥塑的建築,一個在大溪建成商行一個在桃園市,待查詢

談談strip

| Comments

Strip,顧名思義,就是脫脫。有興趣的紳士可以估狗strip club。那麼在Linux的binutil中strip是要脫什麼呢?先來問一下男人

man strip
1
2
3
4
5
STRIP(1)                                    GNU Development Tools                                    STRIP(1)

NAME
       strip - Discard symbols from object files.
...

用中文說,就是從object 檔中把symbol丟掉。讓我們做幾個小實驗吧。

測試環境

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

測試程式

main_test.c
1
2
3
4
5
6
7
8
9
#include <stdio.h>
extern test();

int main(void)
{
    test();

    return 0;
}
test.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>

char *g_myStr = "Wen";
static char *gp_myStr = "Liao";

static void s_test(void)
{
    printf("%s %s\n", g_myStr, gp_myStr);
}


void test(void)
{
    printf("Hello ");
    s_test();
}
Makefile
1
2
3
4
5
6
7
8
9
10
TARGET=test
SRCS=test.c main_test.c
OBJS=$(patsubst  %.c, %.o, $(SRCS))
CFLAGS=-g

$(TARGET): $(OBJS)
  $(CC) $(CFLAGS) $^ -o $@

clean:
  rm -rf $(TARGET) $(OBJS)

測試一:Strip 執行檔

1
2
3
4
5
6
7
8
9
10
11
12
13
$ make clean
rm -rf test  test.o  main_test.o

$ make
cc -g   -c -o test.o test.c
cc -g   -c -o main_test.o main_test.c
cc -g test.o main_test.o -o test

$ ./test
Hello Wen Liao

$ ls test -gG
-rwxrwxr-x 1 10217 Nov 16 22:33 test

可以看到產生出來的執行檔有10217 bytes。我們進一步來看執行檔的symbol。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ nm test
0000000000601050 B __bss_start
...
0000000000601040 D g_myStr
00000000004003e0 T _init
...
0000000000601048 d gp_myStr
...
000000000040056d T main
                 U printf@@GLIBC_2.2.5
...
000000000040052d t s_test
0000000000400553 T test
0000000000601050 D __TMC_END__

那麼來看看strip後的檔案size和symbol吧。你可以看到size變小而且symbol不見了。另外上面可以比對一下tTdDs_testtestgp_myStrg_myStr的關係。

1
2
3
4
5
6
7
8
9
10
$ strip test

$ ./test
Hello Wen Liao

$ nm test
nm: test: no symbols

$ ls -gG test
-rwxrwxr-x 1 6296 Nov 16 22:36 test

測試二:Strip Object檔

因為strip就是把object file (執行檔也是一種object file)的symbol拿掉,所以在link time需要symbol時如果該object檔案被strip過,就會發生錯誤。範例如下:

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
$ make clean
rm -rf test  test.o  main_test.o

$ make
cc -g   -c -o test.o test.c
cc -g   -c -o main_test.o main_test.c
cc -g test.o main_test.o -o test

$ nm -a test.o
0000000000000000 b .bss
0000000000000000 n .comment
0000000000000000 d .data
0000000000000000 N .debug_abbrev
0000000000000000 N .debug_aranges
0000000000000000 N .debug_info
0000000000000000 N .debug_line
0000000000000000 N .debug_str
0000000000000000 r .eh_frame
0000000000000000 D g_myStr
0000000000000008 d gp_myStr
0000000000000000 n .note.GNU-stack
                 U printf
0000000000000000 r .rodata
0000000000000000 t s_test
0000000000000026 T test
0000000000000000 a test.c
0000000000000000 t .text

$ ls -gG test.o
-rw-rw-r-- 1 3944 Nov 16 23:02 test.o

$ strip test.o

$ ls -gG test.o
-rw-rw-r-- 1 952 Nov 16 23:03 test.o

$ nm test.o
nm: test.o: no symbols

$ make
cc -g test.o main_test.o -o test
/usr/bin/ld: error in test.o(.eh_frame); no .eh_frame_hdr table will be created.
main_test.o: In function `main':
/home/wen/tmp/sandbox/main_test.c:6: undefined reference to `test'
collect2: error: ld returned 1 exit status
make: *** [test] Error 1

這邊我nm下了-a參數,這會顯示出所有的symbol,預設的nm輸出如下提供比較。

1
2
3
4
5
6
$ nm test.o
0000000000000000 D g_myStr
0000000000000008 d gp_myStr
                 U printf
0000000000000000 t s_test
0000000000000026 T test

測試三:Strip debug 資訊

其實只是單純要介紹-d參數而已

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
$ make clean
rm -rf test  test.o  main_test.o

$ ls -gG test.o
-rw-rw-r-- 1 3944 Nov 16 23:02 test.o

$ make
cc -g   -c -o test.o test.c
cc -g   -c -o main_test.o main_test.c
cc -g test.o main_test.o -o test

$ ls -gG test.o
-rw-rw-r-- 1 3944 Nov 16 23:02 test.o

$ strip -d test.o

$ nm -a test.o
0000000000000000 b .bss
0000000000000000 n .comment
0000000000000000 d .data
0000000000000000 r .eh_frame
0000000000000000 D g_myStr
0000000000000008 d gp_myStr
0000000000000000 n .note.GNU-stack
                 U printf
0000000000000000 r .rodata
0000000000000000 t s_test
0000000000000026 T test
0000000000000000 t .text

$ make
cc -g test.o main_test.o -o test

$ ./test
Hello Wen Liao

$ ls -gG test test.o
-rwxrwxr-x 1 9737 Nov 16 23:01 test
-rw-rw-r-- 1 1896 Nov 16 23:01 test.o

測試四:Strip shared library

這邊要幹的第一件事是修改Makefile如下。主要是把test.o包裝成shared library,這個Makefile很醜,我知道。

Makefile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
TARGET=test
SRC=main_test.c
OBJ=$(patsubst  %.c, %.o, $(SRC))

LIB_SRC=test.c
LIB_OBJ=$(patsubst  %.c, %.o, $(LIB_SRC))
LIB_NAME=test
LIB=lib$(LIB_NAME).so
CFLAGS=-g

$(TARGET): $(OBJ) $(LIB)
  $(CC) $(CFLAGS) $< -o $@ -L./ -l$(LIB_NAME)

$(LIB): $(LIB_OBJ)
  $(CC) -shared -Wl,-soname,$(LIB).0 $^ -o $@
  rm $(LIB).0 && ln -sf $(LIB) $(LIB).0

$(LIB_OBJ): $(LIB_SRC)
  $(CC) $(CFLAGS) -c -fPIC $^

clean:
  rm -rf $(TARGET) $(OBJ) $(LIB_OBJ)

自幹shared library執行程式的時候不要忘記加上LD_LIBRARY_PATH環境變數:

1
2
3
4
5
6
7
8
9
10
11
12
$ make clean
rm -rf test  main_test.o  test.o

$ make
cc -g   -c -o main_test.o main_test.c
cc -g -c -fPIC test.c
cc -shared -Wl,-soname,libtest.so.0 test.o -o libtest.so
rm libtest.so.0 && ln -sf libtest.so libtest.so.0
cc -g main_test.o -o test -L./ -ltest

$ LD_LIBRARY_PATH=`pwd` ./test
Hello Wen Liao

現在來比較strip前後的shared library 差異吧。

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
$ nm -a libtest.so
0000000000000000 a
0000000000201048 b .bss
0000000000201048 B __bss_start
0000000000000000 n .comment
0000000000201048 b completed.6973
0000000000000000 a crtstuff.c
0000000000000000 a crtstuff.c
                 w __cxa_finalize@@GLIBC_2.2.5
0000000000201030 d .data
0000000000000000 N .debug_abbrev
0000000000000000 N .debug_aranges
0000000000000000 N .debug_info
0000000000000000 N .debug_line
0000000000000000 N .debug_str
0000000000000650 t deregister_tm_clones
00000000000006c0 t __do_global_dtors_aux
0000000000200df0 t __do_global_dtors_aux_fini_array_entry
0000000000201030 d __dso_handle
0000000000200e00 d .dynamic
0000000000200e00 d _DYNAMIC
0000000000000398 r .dynstr
0000000000000230 r .dynsym
0000000000201048 D _edata
00000000000007c0 r .eh_frame
000000000000079c r .eh_frame_hdr
0000000000201050 B _end
000000000000077c T _fini
000000000000077c t .fini
0000000000200df0 t .fini_array
0000000000000700 t frame_dummy
0000000000200de8 t __frame_dummy_init_array_entry
0000000000000840 r __FRAME_END__
0000000000201000 d _GLOBAL_OFFSET_TABLE_
                 w __gmon_start__
0000000000201038 D g_myStr
00000000000001f0 r .gnu.hash
000000000000045c r .gnu.version
0000000000000480 r .gnu.version_r
0000000000200fd0 d .got
0000000000201000 d .got.plt
0000000000201040 d gp_myStr
00000000000005f0 T _init
00000000000005f0 t .init
0000000000200de8 t .init_array
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable
0000000000200df8 d .jcr
0000000000200df8 d __JCR_END__
0000000000200df8 d __JCR_LIST__
                 w _Jv_RegisterClasses
00000000000001c8 r .note.gnu.build-id
0000000000000610 t .plt
                 U printf@@GLIBC_2.2.5
0000000000000680 t register_tm_clones
00000000000004a0 r .rela.dyn
00000000000005a8 r .rela.plt
0000000000000785 r .rodata
0000000000000735 t s_test
0000000000000760 T test
0000000000000000 a test.c
0000000000000650 t .text
0000000000201048 d __TMC_END__

$ ls -gG libtest.so
-rwxrwxr-x 1 9275 Nov 16 23:47 libtest.so

$ strip libtest.so

$ ls -gG libtest.so
-rwxrwxr-x 1 6104 Nov 16 23:47 libtest.so

$ nm -a libtest.so
nm: libtest.so: no symbols

$ LD_LIBRARY_PATH=`pwd` ./test
Hello Wen Liao

這邊變成有新的作業,dynamic link的時候沒有shared library沒有symbol怎麼拿到function address和全域變數?下次有看到再來解釋吧。

補充

如果編譯的程式碼有加入debug資訊,objdump在反組譯的時候可以加入-S參數比對原始碼對應的機械碼,對於想要研究系統細節的人應該有所幫助。簡單範例如下

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
 x$ make clean
rm -rf test  test.o  main_test.o
$ make
cc -g   -c -o test.o test.c
cc -g   -c -o main_test.o main_test.c
cc -g test.o main_test.o -o test

$ objdump -S -d test

test:     file format elf64-x86-64


Disassembly of section .init:

00000000004003e0 <_init>:
  4003e0: 48 83 ec 08             sub    $0x8,%rsp
...

0000000000400553 <test>:


void test(void)
{
  400553: 55                      push   %rbp
  400554: 48 89 e5                mov    %rsp,%rbp
    printf("Hello ");
  400557: bf 24 06 40 00          mov    $0x400624,%edi
  40055c: b8 00 00 00 00          mov    $0x0,%eax
  400561: e8 aa fe ff ff          callq  400410 <printf@plt>
    s_test();
  400566: e8 c2 ff ff ff          callq  40052d <s_test>
}
  40056b: 5d                      pop    %rbp
  40056c: c3                      retq

...

參考資料

  • Binary Hacks:駭客秘傳技巧一百招

空間母語 - 台灣老街特色篇

| Comments

以下為課程筆記,我沒有專業能力判斷是否有誤解,請自行斟酌。

  • 早期職業主要分為士農工商。而
    • 士農住三合院或四合院
    • 工商住在市區內,主要是街屋
  • 本次活動主要討論街屋,三合院、四合院不在討論範圍。
  • 早期街屋考量風水,會在街道前後左右出入口安排在廟宇、山川、牆等位置,希望氣不要漏掉。
  • 台灣靠海風大、北部雨多,在這些地區會看到不見天街
  • 街道的兩側的立面就像是臉一樣,各種的裝飾就像面具一樣,發揮自己的個性。
  • 樓井的樓梯入口一般來說不會面對入口方向,原因是風水考量不希望氣跑掉。
  • 過水:兩進(兩落)之間的空間,一般廚房、水井安排在這邊。
  • 陳旺來、陳三川(郭三川):日治時期的土水師傅,曾經參與大溪老街、迪化街老街街屋、葉金塗宅立面工程。
  • 街屋中,單拱的難度比較高,沒有處理好會出現裂縫。
  • 圓形磚造柱子會使用弧線磚,該磚頭是大正年間才有產出。
  • 弧線立面是巴洛克建築元素之一。
  • 立面工法之一:使用以焦炭為主的材料,捏成丸狀,往特定區域砸,造出凹凸不平的效果。此工法可以去斗六看。
  • 立面工法之二:將黏性物質糊在平面上,再覆蓋上紙。等待一段時間後用力撕開紙張,造出凹凸不平的效果。
  • Palladio建築
  • 台灣工匠受日本影響
    • 刨刀和中國不同,沒有兩邊的握柄。另外操作刨刀方向是朝身體內側刨,而不是朝身體外側刨。
    • 鋸子兩邊都有齒,一邊是粗的,用來鋸和木頭紋理同方向;而細的齒則是用來橫鋸木頭紋理方向。傳統的情況,是會有兩個鋸子,一個是粗的,另外一個是細的。

參考資料

鹿港蔡爌肉

| Comments

因為對於爌肉的好奇心,徵得在鹿港蔡爌肉的小岳的同意後寫出這篇訪問。小岳的攤位位置在果菜市場,從入口進入後靠文聖街方向第二間攤位。

小岳的長輩原本是做外燴辦桌生意,到了父親這代在鹿港頂番婆這個地方開始了爌肉生意,後來才把攤位搬到鹿港。目前攤位除了有爌肉飯、豬腳外、也有提供各式配菜和肉燥飯等料理。

在「彰化小食記」這本書有提到正統的彰化爌肉是使用腿庫、三層肉部份只是備料給少數喜歡三層肉的客人。這樣的觀察可以在蔡爌肉的料理中完全驗證。接下來我們來看看爌肉的料理方式吧。

凌晨四點多小岳一家就要起床準備開張,他們就在攤位現場料理,使用了非常先進的開放廚房概念。開張以後就開始料理菜色,大概五點多就可以點菜了。一般來說市場大概早上七點前會比較忙碌,如果估計第二天備料不足的時候,就要在早上七點多和肉商拿肉。主要是腿庫(豬大腿)、三層、還有腹部等部位的連皮肥肉。接下來把這些肉帶回後,要做料理前的處理。

首先第一步要川燙:

川燙的時候要隨時小心不要被濺起來的滾水燙到。

什麼時候可以起鍋也是全憑經驗。

起鍋後先浸冷水稍微冷卻一下後放置在另外一個臉盆內。

冷卻完畢後,就可以進入下一個切塊階段。

切塊主要要處理三個部份

腿庫

  • 將帶皮肥肉和瘦肉使用竹籤固定

  • 帶皮肥肉不一定和瘦肉不一定是同一塊部位。照「彰化小食記」的說法,這樣有一個多的好處,那就是可以隨意調整肥瘦比例

這邊有幾個特殊的部位可以介紹

  • 圈仔(摳ㄚ)
    • 腿庫膝關節那端的部位,一隻腿庫只有兩個。
  • 離緣
    • 大腿的某個部位,由肥肉包圍的含脂肪瘦肉部位,一隻腿庫只有一個。
  • 腳筋
    • 一隻腿庫只有一個。

三層

切成片狀後以竹籤固定。固定部份沒有照片。另外小岳媽媽推荐的部位照片中有標示,腿庫的離緣紋路和這邊標示很類似。

肉燥原料

直接看照片就知道了。

成品欣賞時間

接下來就是隔天凌晨的料理時間了。因為料理如作戰不方便打擾,加上我爬不起來就跳過了。那麼就讓我們來欣賞最後的成品囉。

  • 爌肉加上肉燥飯

  • 紅糟肉飯

  • 腳筋

致謝:

感謝小岳慷慨允許我問東問西並且拍照、親切的小岳媽媽介紹我不同的爌肉部位、以及熱情活潑的小岳妹妹現場示範食材準備。

地圖


查看更大的地圖

街景

台北城市散步 - 老房子的秘密筆記

| Comments

  • Nov/25/2015: 收到朱禹潔老師指導信件,補充及修正了部份內容。

感謝朱禹潔老師詳細的說明、不厭其煩地回答我的問題、和在整理筆記時提供協助。整理資料如下:

歷史建物定義、維護的相關法規

  • 亞洲和西方老建物最大的差別是亞洲以木構造為主的亞洲建築物,因木材本身易損壞且須更換,與石造為主的西方建築物差別很大。1994年「奈良真實性文件」就是世界遺產組織修正以石造建築為主的真實性認定方式。
  • 台灣建築相關文化資產分類
    • 古蹟
      • 建築更動彈性差,就算要加入一個釘子需要成立審查委員會
    • 歷史建築
    • 聚落
    • 文化景觀
    • 遺址
  • 其他文化資產還有
    • 傳統藝術
    • 民俗及有關文物
    • 古物
    • 自然地景
  • 老建築管理法規有文化資產保存法和都市規劃法這兩個法規
  • 建物修復保存流程
    • 反應/申請
    • 成立審議委員會
    • 評估條件
      • 代表性
      • 價值
      • 建築物價值
      • 住過的人造就建築物的價值
        • 這幾年來逐漸增多,一些名人故居指定為古蹟,或登錄為歷史建築會基於這樣的理由如蔡瑞月舞蹈研究社
    • 指定為古蹟/歷史建築
    • 古蹟修復及再利用規範,整理自古蹟修復及再利用辦法
      • 修復或再利用計畫
      • 規劃設計
      • 施工
      • 監造
      • 工作報告書
  • 保存和維護是不同的面相
  • 文化資產修復的建築師事務所及營造廠原本不多,造成寡佔市場。2008年開放許多建築師事務所及營造廠具有修復設計及工程承攬資格,造成修復品質良莠不齊。曾經有廠商因為不知道量測木頭含水量而造成修復後木頭龜裂,最後只好重做。而原本的修復也是難以評估品質

日治時代建築特色

  • 日本使用鋼筋混凝土時間早於米國。1923年關東大地震過後,磚造承重牆的建築被淘汰,鋼筋混凝土逐漸普及。 當初使用的鐵筋,密度和網格與今日不同 ,目前台灣文學館有樣本供人參考比對。。
  • 現在修復日式建築的紅檜是從日本進口,只是那些進口的紅檜也是台灣紅檜出口轉內銷,而且還需要特殊管道才能取得。
  • 日治時代建築分類
    • 新藝術、式樣
    • 擬洋風/和洋風
    • 現代(指構造、建材)
    • 純日式
  • 日治時代以台灣作為建築設計的實驗場,所以可以看到不同的嘗試,可行後再拿回日本使用。所以同樣的日本設計師可能在台灣的設計風格和之後在日本的設計風格差異很大。
  • 在日本一般溼度約60%,但是在台灣溼度超過60%比比皆是,甚至可以到90%。造成使用日本傳統工法的建築在台灣容易損毀。因此日治時期針對台灣潮溼的氣候在建築上的改變有
    • 抬高地基隔絕溼氣
    • 加寬加深陽台
  • 日式木造建築特點
    • 雨淋板
    • 編竹夾泥
    • 鬼瓦/黑瓦
  • 日式木造建築建造順序
    • 磚石建立基底
    • 梁柱
    • 屋頂
    • 壁體
      • 編竹夾泥
      • 編竹夾泥上灰
      • 雨淋板
  • 鳥居分類三種
    • 神明鳥居
    • 明神鳥居
    • 稻禾鳥居

其他

  • 抿石子:山海樓使用的工法
  • 噴蛭石:米國前在台領事館有使用到的施工方式之一
  • UNESCO:聯合國教育、科學及文化組織
  • 三橋町
    • 日治時代重要人物如乃木希典總督之母、日本總督明石元二郎和日本人的墓地,約在林森公園、康樂公園附近。戰後為臨時居住地,整頓後目前有保留兩座鳥居置於林森公園內。當初成立公園時有抗爭運動、可惜沒有保留墓地的聲音。
    • 有學員補充臨時居留區以林森北路為界,西邊為戰後新住民加上原住民配偶為主,而東邊為戰前住民北上時的聚落。
    • 明石元二郎
      • 在他任內創立台灣電力株式會社、架設縱貫鐵路、以及建立各地職業學校。

參考資料

註解中使用Javadoc Tag並將其轉成pdf 說明文件

| Comments

目錄

前言

javadoc 是一套java程式碼文件產工具。它透過在程式碼註解置入javadoc專用的tag,讓自動產出的文件內容提供更詳細的資訊和美觀的呈現方式。這邊整理資訊如下

在開始加入前,我們可以想一下你要看SDK文件會想看什麼,我自己會想到

  • 方塊圖/元件簡介
  • 函數詳細說明如
    • 函數名稱
    • 函數功用
    • 函數參數說明
    • 函數回傳值
    • 函數使用注意事項
    • 和函數功能相關的其他函數。比如說看到play你會想看openpausestopseek等。

接下來看看我查到的javadoc工具能夠幫我們做到那邊吧?

註:時間和能力有限,有些功能也許沒找到是因為自己沒看到而不代表javadoc辦不到,請讀者自行斟酌。

測試環境和程式

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

檔案和目錄結構

1
2
3
4
5
6
7
8
9
10
11
12
$ tree
.
├── com
│   └── test
│       └── test.java
├── config.properties
├── Makefile
└── pdfdoclet-1.0.3
    ├── build.xml
       ....
    └── jar
        └── pdfdoclet-1.0.3-all.jar

Makefile

就單純編譯一個java檔,以及對應的pdf文件,並使用Ubuntu 上面預設的pdf閱讀程式開啟之。pdf文件使用pdfdoclet產生。該套件需自行下載,我將下載的pdfdoclet解壓縮到測試程式同一層目錄中。

Makefile
1
2
3
4
5
6
7
8
9
10
11
12
13
SRC_PATH=com/test
TARGET=$(SRC_PATH)/test
SRC_FILE=$(addsuffix .java, $(TARGET))

$(TARGET): $(SRC_FILE)
  javac $^
  javadoc -docletpath pdfdoclet-1.0.3/jar/pdfdoclet-1.0.3-all.jar \
            -doclet com.tarsec.javadoc.pdfdoclet.PDFDoclet $^ -verbose \
            -pdf $@.pdf -config config.properties
  evince $(TARGET).pdf

clean:
  rm -rf $(SRC_PATH)/*.class $(TARGET).pdf

pdfdoclet config檔案

config.properties
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
# Prints @author tags if set to "yes".
tag.author=yes

# Prints @version tags if set to "yes".
tag.version=yes

# Prints @since tags if set to "yes".
tag.since=yes

# Show the Summary Tables if set to "yes".
summary.table=yes

# Encrypts the document if set to "yes".
encrypted=yes

# The following property is ignored
# if "encrypted" is not set to yes.
allow.printing=yes

# Creates hyperlinks if set to "yes".
# For print documents, use "no", so
# there will be no underscores.
create.links=yes

# Creates an alphabetical index of all
# classes and members at the end of the
# document if set to "yes".
create.index=yes

# Creates a navigation frame (or PDF
# outline tree) if set to "yes".
create.frame=yes

# Creates a title page at the beginning
# of the document if set to "yes".
api.title.page=yes

# Defines the title on the title page if
# no external HTML page is used.
api.title=Test pdfdoeclet

# Defines the author text on the
# title page.
api.author=by Wen Liao

測試原始碼

印出九九乘法表,我不會Java,更不會OO。這個程式示範了在class中呼叫另外一個class method。如果有人有發現寫不好的請指正,感恩!

com/test/test.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.test;

public class test {
    public class mul {
        public int multiply(int i, int j) {
            return i * j;
        }
    }

    public static final int MAX_NUM = 9;
    public static void main(String[] args) {
        test my_test = new test();
        my_test.hello();
    }

    private void hello() {
        mul my_mul = new mul();
        for (int i = 1; i <= MAX_NUM; i++) {
            for (int j = 1; j <= MAX_NUM; j++) {
                System.out.println(i + " x " + j + " = " + my_mul.multiply(i, j));
            }
        }
    }
}

javadoc 專用註解說明

還沒加入javadoc註解的pdf檔如link,應該沒病毒吧?。接下來讓我們開始吧。

要引入javadoc的註解,要加入javadoc的註解專用格式如下。

javadoc註解
1
2
3
/**
* 兩個星號開頭的註解,這行的第一個*可以省略
*/

在介紹tag前,從參考資料中整理了一些注意事項

  1. 一個javadoc 註解可以大概分成兩個部份
    • 主要文字描述部份,描述特定的class, interface, method是做什麼,操作時有哪些需要注意的事項。
    • tag section,除了註解開頭的*外,一行以@開頭的javadoc tag註解。所有的主要文字描述必須要放在tag section前面。
      • tag又可以分為
        • block tag,就是上面講的@放在行首的情形
        • inline tag,以{@java_tag_名稱 參數}表示
  2. 第一個@符號出現在單行最開頭之前的所有註解,視為該註解往下最接近的method說明。
  3. 註解中兩個段落以<p>間隔。
  4. javadoc註解的開頭使用簡潔的語句描述下面實體如class/method等。想像成git commit log第一行對於git log的重要性。
  5. 註解中可以插入HTML語法,包括<a href...><img src=...><pre>...</pre>。因為這樣,要顯示<&之類字元的要使用HTML Entities&amp
  6. javadoc註解文件分類
    • doc 文件,在class, interface, method等宣告前的註解,用來解釋該class、interface等。
    • overview 文件,整個產出文件的簡介說明。
    • package 文件,單個package的簡介文件。

以下是典型的javadoc 註解範例

1
2
3
4
/**
* 說明你的method
* @param 參數說明,這就是一個javadoc tag,*可有可無。
*/

上列第一項的規範,引起另外兩個問題:

  • 一個package的要怎麼說明呢?
  • 更大的狀況,整個套件有多個package要怎麼說明呢?

關於上面的情況,參考文件有提到:

  • package的說明有兩種,請二擇一
    • 寫在package同目錄中的 package-info.java
    • 寫在package同目錄中的 package.html
      • 請把javadoc的註解格式放在<body></body>之間
  • 整個套件的說明有兩種,需要寫一個html檔案,把你的說明放在裏面。並且在執行javadoc加入-overview 你的套件html檔名

很複雜嘛?看個範例,更改上面的範例

  • Makefile,新增參數是指定-overview的檔案
新增package和overview文件的 Makefile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
SRC_PATH=com/test
TARGET=$(SRC_PATH)/test
SRC_FILE=$(addsuffix .java, $(TARGET))
SRC_FILE+=package-info.java
OVERVIEW_FILE=overview.html

$(TARGET): $(SRC_FILE)
  javac $^
  javadoc -docletpath pdfdoclet-1.0.3/jar/pdfdoclet-1.0.3-all.jar \
            -doclet com.tarsec.javadoc.pdfdoclet.PDFDoclet $^ -verbose \
            -pdf $@.pdf -overview $(OVERVIEW_FILE) -config config.properties
  evince $(TARGET).pdf

clean:
  rm -rf $(SRC_PATH)/*.class $(TARGET).pdf

新增兩個檔案

  • overview.html
overview.html
1
2
3
4
5
6
<html>
<body>
This a overview document for testing. It should supports javadoc overview tags. However, somehow my pdfdoclet did not get javadoc tag to work while javadoc generated file has no problem at all. There might some missing actions.

</body>
</html>
  • package-info.java
package-info.java
1
2
3
4
5
/**
 * This is a page to describe your package
 */

package test;

現在產出的pdf檔如link,應該沒病毒吧?

javadoc 專用註解tag節錄

偷懶不列support 版本,需要自行找尋相關資料

tag名稱 功能
@author 作者名稱 請望文生義,預設不會放在產出網頁,需要透過command line才會出現在文件中
{@code 程式碼} 塞入程式碼
{@docRoot} 將會被展開替換成文件的最上層目錄,需要link到本機相對路徑檔案時很好用
@deprecated 作廢說明文字 作廢的API說明
{@inheritDoc} 當函數宣告的參數、回傳值或是expcetion沒說明時,這邊可以指定從那邊繼承。這些繼承關係也有些預設選項,一言難盡。這邊先跳過,有興趣的請自行找資料。
@param 參數名稱 說明 請望文生義
@return 回傳值說明 請望文生義
@{value} 顯示註解下方變數的常數
@since 文字,通常是版號 指出該method, package, class等在哪個版號後有效

由於有些tag放到表格不易閱讀,獨立說明如下

  • {@link package_名稱.class_名稱#member_函數名稱 顯示在文件的字串}
    • 在文件中產生package名稱.class名稱#member_函數名稱的link
  • {@linkplain package_名稱.class_名稱#member_函數名稱 顯示在文件的字串}
    • 在文件中產生package名稱.class名稱#member_函數名稱的link。但是顯示的文字不會有link
  • @exception class-名稱 exception_說明
    • 應該可以望文生義,另外@throw 也是同樣效果
  • @see 家族,有三種
    • @see "字串"
      • 請讀者參考字串
    • @see <a href="URL#value">Link顯示名稱</a>
      • 請讀者參考連結
    • @see package_名稱.class_名稱#member_函數名稱 顯示名稱
      • 建立本地連結到package名稱.class名稱#member_函數名稱

套用 javadoc 範例

最後的檔案和目錄結構
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ tree
.
├── com
│   └── test
│       └── test.java
├── config.properties
├── Makefile
├── overview.html
├── package-info.java
└── pdfdoclet-1.0.3
    ├── build.xml
       ....
    └── jar
        └── pdfdoclet-1.0.3-all.jar
加了javadoc的程式碼
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
package com.test;

/**
 * A example to test javadoc tags
 * <p>
 * @see mul
 * @see test.mul#multiply
*/
public class test {
    /**
     * multiply class, used by {@link test class test}
    */
    public class mul {
        /**
         * @param i operand 1
         * @param j operand 2
         * @return result of i * j
        */
        public int multiply(int i, int j) {
            return i * j;
        }
    }
    /**
     * Number of loop in multiplication: {@value}
    */
    public static final int MAX_NUM = 9;

    public static void main(String[] args) {
        test my_test = new test();
        my_test.hello();
    }

    private void hello() {
        mul my_mul = new mul();
        for (int i = 1; i <= MAX_NUM; i++) {
            for (int j = 1; j <= MAX_NUM; j++) {
                System.out.println(i + " x " + j + " = " + my_mul.multiply(i, j));
            }
        }
    }
}

最終產出的pdf檔如link,應該沒病毒吧?

參考資料