顯示具有 linux 標籤的文章。 顯示所有文章
顯示具有 linux 標籤的文章。 顯示所有文章

2020年5月16日 星期六

第一次跳槽 vscode 就上手

故事是這樣子的,小弟第一次學寫 code 的時候,是在大一修計算機程式(嚴格來說是高三下學期上了幾個小時的 C,不過那實在稱不上是"學")的時候,第一個使用編輯器是破舊破舊的 Dev C++ ,我打這篇的時候差點都忘了它叫 Dev C++ 了。
當然那時候的功力跟現在實在是天差地遠,淨寫一些垃圾,啊雖然現在也是淨寫一堆垃圾…。
總之後來應該是大二,被同學們拉去演算法課上當砲灰,第一次接觸了工作站 + vim,從那時候把 Dev C++ 給丟了跳槽到 vim,就一直用到現在,之中當然也會用一下其他的編輯器,像是改 windows 的 .NET 程式用到 Visual Studio,但大體還是以 vim 為主力,算算也是超過 10 年的 vimer 了。

不過這兩三年在工作上、日常 project 上面,多多少少都見識到 vim 的不足之處,例如新語言(主要是 Rust)支援不足、跟編譯除錯工具整合不佳、跟 GUI 整合不佳、跟 Git 整合不佳要另外開終端機跟 gitg、自動格式化/排版操作麻煩而且通常排不好;正好此時 Microsoft 回心轉意擁抱開源,推出了 vscode,隔壁棚的 emacs 有大神跳槽鬧得風風雨雨,台灣 CUDA 第一把交椅強者我同學 JJL 也跳槽 vscode 惹還來傳教。

正好最近寫 code 沒什麼靈感,而且最近正好武漢肺炎的關係時機歹歹,就來試著跳槽一下吧(?,到目前為止用 vscode 對最近碰的一個 ncollide package 做了一些除錯的工作,筆記一下到目前為止的設定還有使用方式的筆記。

vscode 基本上的優勢就是是它編輯/建構/除錯三位一體的編輯介面;還有它的擴充功能,用過的都說讚。
擴充方面主要參考的文件有兩個:VSCode 如何提高我的寫扣效率小克的 Visual Studio Code 必裝擴充套件,另外台灣 CUDA 第一把交椅強者我同學 JJL 大大也有推薦一些:

擴充的安裝方式是按快捷鍵 Ctrl + P,打入 ext install 後面接套件名,下面擴充的連結裡面也有顯示安裝的指令:

vim 擴充,讓 vscode 的編輯介面套用 vim 的操作方式,想要手跟鍵盤黏踢踢就一定要裝
語言相關:
C/C++ 擴充:還沒試用只是覺得起家的 C++ 必須裝一下:
Python 擴充:一樣還沒試用只是覺得有一天會寫到先裝一下:
Rust 擴充:這個是這次語言唯一試用過的,雖然結果不怎麼樣
codelldb 除錯擴充,可以用 LLVM 的 lldb 對程式除錯,裝了這個是為了要對 Rust 除錯

工具類:
Git Graph:整合 gitg 類似的圖形化顯示工具到介面,git 管理上當然可以靠打字,但看歷史還是看圖方便
GitLens:還沒試過,強者我同學 JJL 推薦的
TODO tree:統一管理 project 內部的 TODO, FIXME, XXX
Trailing Spaces:自動刪掉程式碼行尾的空白
Markdown Github Style:編輯 markdown 文件時可以直接預覽輸出的格式,解決每次編輯 Github README.md 都要一直 push -f 直到格式完全改對為止,這點很強烈的突顯出 vim 等純文字編輯器的弱項,無法和圖形整合,以致在 markdown、LaTex 這類文字和顯示有相互關係的文件編輯會很吃虧(好啦好啦我知道有人能人腦 render latex 的)。

怎麼建構專案?
在 vscode 裡面的建構叫 task,在選單 terminal 下面的 run Task 跟 run Build Task (Ctrl + Shift + B),沒有 cargo 預設的話就要自行編輯 tasks.json,以下是我這次 debug 時使用的 tasks.json
{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "cargo run",
      "type": "shell",
      "command": "cargo",
      "args": ["build"],
      "group": {
        "kind": "build",
        "isDefault": true
      }
    }
  ]
}
應該滿直覺的,就是呼叫 cargo build 幫我編譯整個專案;在寫完 code 之後使用快捷鍵 Ctrl + Shift + B 就能編譯專案了。

如何除錯:
除錯是 vscode 一項殺手級的功能,vscode 公開一個 API 讓安裝的語言擴充使用,需要什麼語言的除錯安裝擴充就好,像我上面就安裝了 C/C++, Python, Rust 的擴充。如果我記憶沒錯的話,跟 visual studio 一樣,vscode 快捷鍵是也執行 Ctrl + F5 跟除錯 F5:

用 Ctrl + Shift + D 展開 debug 介面。
理論上用滑鼠在原始碼旁邊點一下就能加上 breakpoint 不知道是 Rust 還是 lldb 的問題,我用滑鼠加上去的 breakpoint 都煞不住,至少一定要先在 debug console 裡下一個 b main 讓程式煞住之後,用滑鼠加的 breakpoint 才會有用,真的很奇怪。
另外就是 debug console 的指令跟習慣的 gdb 有點不同要重新習慣,最奇怪的大概是按了 enter 竟然不會重複上個指令,這樣要一直按 n + enter + n + enter 怪麻煩的,只能去習慣 vscode 的除錯指令:F10/next、F11/step、Shift+F11/finish 了。

這次最主要的目的是要對 Rust 程式除錯,我參考的是下面這篇文章…不過試用之後沒有成功
首先我們要加上一個 launch.json 告訴 vscode 要怎麼跑除錯的程式:
{
  "version": "0.2.0",
  "configurations": [
  {
    "name": "Debug example contact_query2d",
    "type": "lldb",
    "request": "launch",
    "program": "${workspaceRoot}/target/debug/examples/contact_query2d",
    "args": [],
    "cwd": "${workspaceRoot}",
  }]
}
再來用 F5 就能開始除錯了,但不知道為什麼我 step into 一個函式,瞬間都變成 assembly code,連 stack 資訊都爛掉了,根本無從 debug 起,感覺是 vscode 哪裡跟 lldb 沒弄好,不過我覺得這不是 vscode 的問題,畢竟我在終端機用 rust-gdb 一樣會有問題,正好反過來如果下 b main 停下來的話,rust-gdb 下一步會停不下來,一口氣跑到 main 的尾巴…。
這個問題一時之間好像無解,也許要等 rust 跟 codelldb/gdb 真的接好之後再來看看了。

下面就是個零碎操作:
Ctrl + KT 叫出 color theme 設定,我用的是 light 的 Solarized Light ,最近眼睛好像不太適合全黑的畫面了QQ。

回頭一看怎麼一堆快捷鍵,不過算啦,跟 vim 的快捷鍵比起來這還是算少的吧XD;話說大概是「把手留在核心區」這個哲學的關係,vim 大部分的按鍵都少有用 Ctrl/Alt 開頭的,剛好一般的圖形應用程式包括 vscode ,大部分的快捷鍵都是 Ctrl/Alt 開頭,也因此在操作上面,vim 很容易就能跟桌面應用程式整在一起,就像是瀏覽器的 vimium 跟 vscode vim 擴充。

2020年4月11日 星期六

用 docker container 來編譯程式

故事是這樣子的,最近受朋友之託研究一個套件,在編譯的時候…不知道為什麼我的 Archlinux 編不起來,有某個奇怪的 bug 蒙蔽了我的雙眼擋住了我而且一時之間解不掉,目前看起來像是 golang 那邊的鍋。
總之目前看起來像是 Archlinux 限定的問題,如果裝一台 ubuntu 18.04 的虛擬機,在裡面 build 就沒這個問題,可以完成編譯。 不過想想,現在都 2020 年了,怎麼連 docker 怎麼用都還沒學起來(查一下這玩意 2013 年就已經問世了耶…),就本例來說沒事還要開一個巨大的 virtualbox ,建個巨大的虛擬磁碟再安裝作業系統真的有點划不來,就花了點時間學了一下 docker ,然後順便記個筆記,不然這年頭發現自己學習能力低落,連 docker 這麼簡單的東西都學不好QQ。

其實網路上已經有 100 篇 docker 的教學文了,不過我還是來寫個 101 篇吧。
首先是關於 docker,大抵上就是一個輕量化的容器,在主機作業系統之上為每個應用程式建立一個最小的執行環境,每個 container 都是一個 user space process;相對的虛擬機則是把整個作業系統都包進去,每個虛擬機共用一個硬體,這部分就偷用一下他們官網的圖片。


當然我們這裡只是要用,背後的原理要是我哪天學會的話再來寫文章記錄QQ,對比虛擬機 docker 具有小、快的優點,畢竟不用開一台機器就要裝一次作業系統,很適合像我這樣只是要用另一個作業系統做個測試,或者寫網路服務的,可以讓程式跑在一個固定的環境裡面,不用一台一台虛擬機處理環境的問題。

這次我的目標就是開一個 ubuntu 18.04 的作業系統,然後在裡面進行編譯。 以我的 archlinux 來說,第零步是先安裝並啟動 docker:
pacman -S docker
systemctl start docker.service

為求方便的話可以把自己加入 docker group 裡面,不過這等同於給他 root 權限(這段警語只出現在英文 wiki 上面):
gpasswd -a user docker

第一步當然就是先把 ubuntu 18.04 的 image 給拉下來,不加版號的話會拉下最新的版本,這裡的 ubuntu image 是 ubuntu 官方準備好,並且放到 docker hub 上面供大家下載的版本,是一套非常純粹的 ubuntu,映像檔最小化連 python 都沒有;大家可以視自己的需求選擇其他的版本,像是 node 官方也有出自己含 node.js 的映像檔,python、django、mysql …都有對應的映像檔可以選擇。
如果自己註冊 docker hub 的帳號,也可以把自己建構的映像檔上傳到 docker hub 上讓大家下載,不過我這篇不會介紹,有興趣的請自己參考這兩篇:。 
docker pull ubuntu:18.04 docker pull ubuntu

載好之後就可以在 docker image ls 或 docker images 看到 ubuntu 了:
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu 18.04 4e5021d210f6 3 weeks ago 64.2MB
ubuntu latest 4e5021d210f6 3 weeks ago 64.2MB

有了 image 就可以把 container 給跑起來,可以想像 image 就是把需要的檔案都拿到手裡,把 image 放到 container 裡面跑起來就會變得像一個真的作業系統一樣。
docker run 可能是 docker 最複雜的指令之一,選項多到不可理喻,我們先從簡單的開始:
docker run -it ubuntu:18.04 bash

執行一個 ubuntu 18.04 的容器,-it 讓 docker 打開虛擬終端機,並執行 bash,這時候我們就會進到 ubuntu 的 bash,可以從 lsb-release 裡面看到這真的是一台 ubuntu 的機器。
root@e98deb8ccdaf:/# ls
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
root@e98deb8ccdaf:/# cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=18.04
DISTRIB_CODENAME=bionic
DISTRIB_DESCRIPTION="Ubuntu 18.04.4 LTS"

開另一個 host 的終端機,用 docker container ls 或是 docker ps 也能看到它在運作:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e98deb8ccdaf ubuntu:18.04 "bash" 59 minutes ago Up 59 minutes inspiring_feistel

但這個 container 在我們下 exit 離開的時候,它也會跟著不見,要用 docker container ls -a 把執行中跟已經被關掉的 container 都列出來才會看到它。
這多少顯示了 docker 隨開隨用,不用隨關的特性,下個 run 就開了一個,不用了它就被關掉了。 於是我們可以在 run 的時候,改成這樣下:
docker run -itd --name blogger ubuntu:18.04

首先是 -d 這個參數,會讓 docker 在背景把這個機器給開起來;--name 則是給機器一個別名,這樣就不需要去動到前面 docker container ls 裡面的 CONTAINER ID,畢竟打名字還是比打 hash 的 hex value 簡單多了。
下完這行 docker 會給出新產生機器的 hash value,docker ps 也可以看到:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
35f40d006a5f ubuntu:18.04 "/bin/bash" 1 second ago Up Less than a second blogger

這時候我們可以用 exec 進到這台 container,這樣跟 run -it 的效果是一樣的,只是這次離開 container 之後它還是會繼續執行,blogger 的位置換成它的 container ID 35f4 也可以,以下同:
docker exec -it blogger bash

把它停掉可以用 stop 正常關掉這個 container 或是 kill 直接砍了它:
docker stop blogger docker kill blogger

就算是關掉的 container 在 docker ps -a 還是看得到它,可以用 restart 把它開回來
docker restart blogger

為了要用 ubuntu 的機器編譯,我們還要將外部的檔案放到 container 內部,docker 對應的機制可以用 docker cp,有點像是 scp 的下法:
docker cp : docker cp ~/server.py blog:/server.py docker cp blog:/server.py ~/server.py

或者我們想要簡單一點,可以用 volume 的方式,這有點像是 virtualbox 裡面的共享資料夾,平時 container 跟 host 之間可以用這個資料夾互通有無,且就算 container 被刪掉了,這個資料夾還是會留著;詳細的 volume 介紹可以看這篇,我這裡是直接用它的第二種方式,直接在 run 的時候指定一個資料夾給 container:
docker run -v ~/docker:/docker -it ubuntu:18.04 bash

就能在內部的 /docker 裡面看到 host 那邊 ~/docker 的檔案了:
root@c518b1b9fd7b:/# ls docker/
Dockerfile

如果要用 docker 當個編譯工具的話,差不多是這樣就夠了,連續的指令打起來就是:
docker run -v ~/docker:/docker -itd --name compile ubuntu:18.04
docker exec -it compile bash
root@c518b1b9fd7b:/# apt update ...

全新的 18.04 ubuntu 真的是超級單純,該裝的東西都要自己裝好,連 apt 都要自己 update,但我編譯下去它還真的編譯過了 WTF……到底 archlinux 是出了什麼問題…。

本篇文章感謝強者我同學在新加坡大殺四方稱霸麻六甲海峽的志賢大大多加指導。

2018年3月18日 星期日

實用的gdb 指令

最近工作上大量使用到 gdb,想說來整理一下一些常用的 gdb 使用方式,以及對應的場景;當然,這絕對不是 gdb 完整的功能介紹,只是目前我遇到比較多的使用方式而已。

檢視原始碼:
gdb 的文字介面可以顯示四種視窗:命令,原始碼,組合語言跟暫存器,與視窗相關的鍵盤組合:
Ctrl + x + a 它會分成上原始碼下命令兩個視窗
Ctrl + x + o 切換焦點所在的視窗,如果焦點視窗放在原始碼那邊,命令視窗的一些鍵會無效,例如上下鍵會變成原始碼視窗的上下瀏覽原始碼,得用 Ctrl + P (previous) 跟 Ctrl + N (next) 才能瀏覽歷史命令。
使用Ctrl + x + 1,Ctrl + x + 2 可以設定原始碼視窗分為一個或兩個,連續 Ctrl + x + 2 會依序在原始碼+組合語言、組合語言+暫存器、暫存器+組合語言三種組合中切換。
這功能可以直接看到現在執行到哪裡,特別是進到除錯熱區時,gdb 用 list 指令都會自動加10 行,要印出當下除錯的程式總有點難度,用這個可以徹底排除這個問題;不過我也覺得還沒到除錯熱區的時候,配合編輯器來看原始碼即可。

快捷鍵(這個不止是 gdb,而是大部分 shell 都適用):
除了上述的 Ctrl + p, Ctrl + n,Ctrl + l 可以清空目前的 buffer,Ctrl +w, Ctrl + u 可以刪除命令列上一個單字或全部內容,也滿好用的。特別是 gdb 裡面 shell 下清空 buffer 的指令 clear 沒有辦法用,Ctrl + l 使用機會滿大的(前提是原始碼視窗沒開)。

中斷點設置:
有關gdb 裡面的四個點:breakpoint, watchpoint, catchpoint跟tracepoint,到現在我也只用過前兩個,99 %都是 breakpoint:
breakpoint 又分為永久跟暫時,對應 break/tbreak 後面接要中斷的位置,可以用函數名稱或者 <sourcefile>:<linenum> 兩種格式。
還可以在 breakpoint 的後面加上條件,例如 break <function> if <var> == 10,就能開始 debug 某個狀況下的執行,這在除錯有迴圈或多次呼叫函式的程式時非常好用。
start 指令,自動設置 temporary breakpoint 在主程式開始處,C/C++ 就是 main。
Watchpoint 則是用watch/rwatch 指令設置,可以監看一個變數什麼時候被動過,指令監看的目標可以是變數、記憶體位置(如 watch *0x12345678)或是幾個變數的運算也可以。
watch、rwatch、awatch 分別監看變數寫入、讀取跟讀寫。

無論是breakpoint watchpoint其實都是… breakpoint(watchpoint 有時叫 data breakpoint),可以用 info break 看到所有相關設定,還有例如它已經碰到幾遍之類。
有一個相關的技巧如下,可以計算某個函數究竟遇到幾次:
break foo
ignore <breakpoint num> 100000 //大到遠超過可能執行的數量
continue
等程式執行完就能用 info break 看到這個 breakpoint 被碰幾次。

另外 debug 時,可以利用save breakpoints <filename>把設好的breakpoint 存到檔案裡,下次可以直接source它們,save 的 breakpoint 最好只設在函數名稱上面,行數可能在debug 時變化,下次source 時就會break在不同的地方了。
我習慣上會存成 gdbinit 這樣的檔案,一進 gdb 時 source gdbinit 即可。

修改執行:
執行中可以用
set var <variable name>=<value>
set <memory location>=<value
的方式修改變數/記憶體位址內容。
例如我們發現某個條件被跳掉會導致錯誤結果,可以在 gdb 內用 set 修改變數,使它符合條件,省去重新編譯的麻煩(甚至一些狀況下,修改程式可能動到本來的邏輯,或很難使它符合條件)

跳出函數執行:
指令 finish,執行直到離開當下函數(當然遇到 breakpoint 還是會被擋)。

介面
上面的功能,有些因為最近比較常用 ddd 而非 gdb,也就沒有常用到。
另外強者我同學強強林有推一款 web-based 的 gdb gui:https://github.com/niklasb/webgdb
,據說 vscode 的 debug 功能也滿生猛的,也許找時間都來試試看。

2017年7月29日 星期六

設定 ssh config 讓人生簡單一些

ssh 是工作上的重要工具之一,平時要連進其他電腦、傳送檔案、甚至是上 ptt 都可以用 ssh 達成,這篇文整理一些 ssh 的設定,可以讓 ssh 的使用更簡單一些。

第一個是免打密碼的設定,這個如果有用 github 的話一定很清楚,簡單來說我們可以把電腦的公開金鑰放一份到遠端,登入的時候 ssh 就會不問密碼自動驗證。
步驟如下:
在家目錄下的 .ssh 目錄中,鍵入 ssh-keygen 指令,預設是使用 rsa,如果覺得 rsa 不夠安全可能會被心算解開(誤,可以用 -t dsa | ecdsa | ed25519 | rsa 選擇要用哪種公開金鑰加密法,用 -b 來選擇生成的金鑰長度(不過我下了 ssh-keygen -b 16384 然後它金鑰生不出來…)。
金鑰生成後,會詢問檔案要存在哪,預設就是 .ssh 資料夾;另外會問 pass phrase,我們都用上 public key 就是不想打密碼,除非有安全性考量否則留空即可。
Enter file in which to save the key (/home/garbage/.ssh/id_rsa): /tmp/id_rsa
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
會產生兩個檔案,id_[algorithm] id_[algorithm].pub,從檔名看 pub 自然是公開金鑰了。
接著將 .pub 檔案的內容,複製到伺服器上 .ssh/authorized_keys 裡面,如此一來 ssh 就能免密碼登入了

詳細請參考
https://blog.gtwang.org/linux/linux-ssh-public-key-authentication/

第二個是設定 ssh config,這有點像 ssh 專用的 /etc/hosts,可以幫常連的機器設定別名,甚至是連線時要用什麼動作,以下來看看:
首先,假設我們要連線一台電腦,remotemachine.com 或者是 ip,帳號名稱是 yolo,開的 port 為 9453,要連線要打入這樣的指令:
ssh yolo@remotemachine.com -p 9453
當然如果照這篇文章安裝了 fzf ,某種程度上能大幅緩解連一台機器要打很多字的問題--連機器的指令不會變,用fzf 找出來就是了,但每次都用 fzf 來找還是會花一些時間,這時可以改用 ssh config
在家目錄的 ~/.ssh/config 檔案中,加上以下內容:
Host MyMachine
 HostName remotemachine.com
 User yolo
 Port 9453
ssh MyMachine 就會直接連上 yolo@remotemachine.com -p 9453
這些規則不是寫死的,ssh 的設定優先次序是命令列、.ssh/config、/etc/ssh/ssh_config,設定後照樣可以用 another@MyMachine 來使用其他使用者登入,或者用 -p 來改變連線的埠。
設定檔就是許多 Host 為開頭的區塊,內容為針對該 Host 的設定,上面展示的就是設定預設主機、使用者跟埠,還有許多其他設定可用,像是認證檔的位置、選擇加密方式,詳細可以看 man ssh_config

下面是我查到 ssh config 的契機,平常工作的地方開了一個新的工作室,但平常測試的主機A放在工作室A,兩個工作室的網段不一樣,沒辦法透過 ip 直接連線;為了連線所以在 router 上面鑽一個洞,開一個 ip 會直接進到工作室A的另一台主機B,到主機B就進到內網,可以直連測試主機A。
如果要打指令,大概會長得像下面這樣,等於是透過machineB ,開一個 pseudo-tty,再執行 ssh進到machineA:
ssh -t userB@machineB.ip -p 9453 ssh userA@machineA.ip
要簡單一點找到這個 stackoverflow,關鍵字是ssh proxy,在 .ssh/config 裡面加上這些設定
Host machineB
  Hostname machineB.ip
  User userB

Host machineA
  User userA
  ProxyCommand ssh -q machineB nc -q0 machineA.ip 22
ProxyCommand 指定要連到這台 Host 時要下的指令為何,這裡會用 ssh quiet mode ,再執行 netcat 接入machineA,這樣只要用 ssh machineA 就能完全上述工作啦
當然這樣每次連線都要打兩次密碼,不想打密碼,照上面在機器上放入自己的公鑰即可。

2017年5月16日 星期二

如何調整 virtualbox 虛擬硬碟檔案的大小

故事是這樣子的,一直以來我都是用 Archlinux 作為我的作業系統,因為在碩士班要跑的模擬用的是 windows 版的ADS,做投影片要用到 M$ Office,於是就裝了一個 Virtualbox ,裡面跑 Win 7。
最近wannacry 勒索軟體肆虐,我才驚覺原來我 virtual box 裡的 win 7 已經超級久沒有更新了,結果是卡到 win 7 的一個 update bug,一更新它就卡在check update 上出不來了,後面搜尋了一段時間,先手動裝了幾個 update 才開始更新,這部分用<windows 7 check update stuck>當關鍵字還不少搜尋結果。
因為累積的一籮筐的更新,要下載的檔案大小超過1 GB,然後…嗯…我的 virtualbox 劃給 windows 的磁碟就滿了(._.),幸好我用的是vdi格式可以動態調整磁碟大小,之前就有調整過一次,這次做個筆記方便下次查找。

https://forums.virtualbox.org/viewtopic.php?f=35&t=50661
開頭就有說了,一定要是 vdi 的虛擬硬碟檔案,首先要先備份一下 vdi 檔,以免調整用量的時候出錯,把整個虛擬磁碟毀了,備份完之後,使用下面的指令,vdi 檔的路徑建議用絕對路徑,size 的單位是 MB,我多劃 5 GB 的空間給它,總共是 40 GB。
vboxmanage modifyhd /media/datadisk/win7.vdi --resize 40960
要注意這個指令似乎只能調大不能調小,所以不要不小心劃太多。

https://www.lifewire.com/how-to-open-disk-management-2626080
vdi 檔案加大之後,只是從 guest 那邊看到的磁碟變大,windows 內還沒有對應的調整,這裡要使用 windows 的 disk management:選控制台->系統及安全性->系統管理工具下有一個建立及格式化硬碟分割,可以把它想成 windows 版的 gparted。
打開之後的畫面大概像這樣,這是已經擴大分割之後的畫面,剛擴大完應該會在 C碟的後面看到另一個未使用的分區。
這時只需要在 C 碟上右鍵,選擇延伸磁碟區,把新加的空間劃給它即可,如此一來就完成了擴大磁碟的工作了。

話說 35 GB 都不夠用,win 7真的肥肥。
更新又這麼久,真的是珍惜生命,遠離 windows。

2017年4月8日 星期六

Makefile header 檔的相依性檢查

Makefile 使用 make depend 進行相依性檢查
在寫Makefile 的時候,一般的規則是這樣的:
Target: Dependency
  Command (Makefile文件是寫 recipe,不過我這邊就寫command)
如果是C 程式,通常也會把 header 檔寫在 dependency 內,否則header 檔改了結果程式沒有重新編譯就奇怪了,如果每次改了header 都要 make -B 也不是辦法。
project 長大的時候,手填 header dependency 變得愈來愈不可行,特別是Makefile 大多是寫 wildcard match,例如這樣把object files 都填入變數 OBJS 之後,直接用代換規則將 .c 編譯成 .o:
SRCS = file1.c file2.c
OBJS = $(SRCS: .c=.o)

target: $(OBJS)
  $(CC) $(LDFLAGS) -o $@ $^

%.o: %.c
  $(CC) -c $(CFLAGS) -o $@ $<
在這裡翻譯了這篇文章,裡面給出一個針對大型專案完整的解決方法,重要的是它有把為何這麼寫的理由講出來:
http://make.mad-scientist.net/papers/advanced-auto-dependency-generation/

最簡單的方法,是在Makefile 裡面加上一個depend 的目標,在這個目標利用一些分析工具,例如makedepend建立 dependency,但這樣等於是把 make depend 跟真正的編譯分開了,缺點有兩個,一是需要使用者下make depend 才會重建 dependency,第二是如果有子資料夾也要依序下 make depend,我們需要更好的方法。

首先我們可以利用 makefile 的include 功能,直接 include 一個含有 dependency 的makefile ,把所有source file 設為 include file 的dependency,這樣只要有source 檔更新,make 時就會自動更新dependency到最新,完全不用使用者介入;但這樣也有缺點,只要有檔案更新就要更新的dependency,我們可以做得更好。

首先是 gnu make 推薦的方法:
https://www.gnu.org/software/make/manual/html_node/Automatic-Prerequisites.html
對每一個原始碼檔,產生一個.d 的相依性資訊
%.d: %.c
  @set -e; \
  rm -f $@; \
  $(CC) -M $(CPPFLAGS) $< > $@.Td; \
  sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.Td > $@; \
  rm -f $@.Td
先開 set -e 設定只要後面的command 出問題就直接結束,然後把 name.d 檔案刪掉;接著使用gcc 的 -M 參數(或者用 -MM ,如果不想要產生的dependency 包含system library的header),會讀入 name.c 之後,產生如下的dependency,寫到 name.d.Td 檔案中。
name.o: name.c aaa.h
之後用sed將上想 gcc -M 產生的內容,加上name.d 這個target:
name.o name.d: name.c aaa.h
對每個.c 檔都產生過 .d 檔之後,就能直接include所有.d 檔案了。
include $(sources: .c=.d)

因為 .d 的dependency 包括對應的 .c 檔,這樣只要.c 檔更新了,makefile 也會重build .d 檔,保持相依資訊在最新,同時只更新需要的 .d ,不會浪費build 的時間。
這個做法有幾個問題,首先是re-invocation 的問題,使用include的問題在於Makefile 本身的設計,因為include 可能帶入新的變數定義,目標等等,一但include 的目標有更新,它就會強制重跑一次本體的Makefile (參考這一篇:http://make.mad-scientist.net/constructed-include-files/ ),這在大型的專案上會帶來可觀的成本;更嚴重的是,如果我們把 .h 檔給刪除或更名,在編譯的時候會出現錯誤:
make: *** No rule to make target 'name.h', needed by 'name.d'. Stop.
這是因為name.d 相依於 .h ,.h 已經不存在而且又沒有產生 .h 的rule,Makefile 就會回傳錯誤;雖然改 .h 的狀況不常見,一但遇到就只能手動刪掉所有 .d 檔重新產生。

為了解決上述兩個問題,首先是Makefile 每次都會重新make 的問題,問題是這樣:如果有個source 更新了要重新編譯,那我們知不知道新的dependency 也沒差--反正它鐵定要重編譯的,該做的是為下一次的編譯產生新的 dependency 資訊;所以我們可以把產生 .d 檔這步,移到編譯 .c 檔的時候,大略如下:
OBJS = $(SRCS: .c=.o)
%.o : %.c
  # 產生 .d 檔的位置
  $(CC) -o $@ $<
include $(SRCS:.c=.d)
第二個問題比較棘手,這次用的招式是:Makefile 中如果有目標但沒有command 或dependency,這個目標又不是個已存在的檔案,那Makefile 無論command有沒有執行,會預設這個目標已經「被更新到最新」,注意它是有更新的,所以相依於這個目標的目標,也會依序更新下去。
http://www.gnu.org/software/make/manual/html_node/Force-Targets.html
例如下面這個例子,因為FORCE 每次執行都會被更新,所以clean 也一定會更新而執行
clean: FORCE
  rm $(objects)
FORCE:
所以這裡用的技巧就是,把那團相依的.h 檔變成無dependency/command 的目標,產生.Td之後,下面我是分成多行的結果,實際上可以寫得緊密一點,所做的工作包括:用 sed 依序移除comment;移除 : 前的target;移除行尾接下一行的反斜線;移除空白行;把行尾變成 : ,這樣本來的dependency 通通變成目標了。
  cp $*.Td $*.d; \
  sed -e 's/#.*//' \
  -e 's/^[^:]*: *//' \
  -e 's/ *\\$$//' \
  -e '/^$$/ d' \
  -e 's/$$/ :/' < $*.Td >> $*.d; \
  rm -f $*.Td
最後的問題是,如果我們移除 .d 檔, .c .h 檔又沒有更新則Makefile 也不會重新產生 .d 檔,因為 .d 檔並不是編譯時相依的target;但 .d 檔又不能是個真的target ,否則又有老問題:include 的時候它會產生 .d 檔,然後整個Makefile 會重跑一次;這裡的解法是把 .d 加到 .o 的dependency ,然後加一個空的target,這樣一來,.d 檔如果存在,因為commands 是空的所以什麼事都不做;如果 .d 被刪除,在make 時 .o 會觸發 .d 的更新,在空的target 更新之後, .o 也跟著更新的時候,一併產生全新的 .d 檔。
%.o: %.c %.d
  command
%.d:
最後是一些針對 dependency file 的處理,完整的Makefile 會長這個樣子:
DEPDIR = .depend
DEPFLAGS = -MT $@ -MMD -MP -MF $(DEPDIR)/$*.Td
POSTCOMPILE = mv -f $(DEPDIR)/$*.Td $(DEPDIR)/$*.d

%.o : %.c $(DEPDIR)/%.d
  $(CC) $(DEPFLAGS) $(CFLAGS) -c $<
  $(POSTCOMPILE)

$(DEPDIR)/%.d: ;
.PRECIOUS: $(DEPDIR)/%.d
include $(patsubst %,$(DEPDIR)/%.d,$(basename $(SRCS)))
第一段定義depend file 的存取地點,然後定義 DEPFLAGS,利用編譯時插入 DEPFLAGS 把編譯跟產生 dependency 一併做完;相關的 flags 都是 -M 開頭,可參考:
https://gcc.gnu.org/onlinedocs/gcc-5.2.0/gcc/Preprocessor-Options.html
  • -MT 定義產生出來的目標名稱,預設是 source 檔的suffix 換成 .o 後綴,並去掉前綴的任何路徑,例如 -MT www 則生成的相依資訊就變成 www: name.h,所以這個選項不一定要加。
  • -MM or -M 要求產生 dependency 資訊
  • -MP 直接幫忙對 header file 產生空的 dependency target (顯然gcc 開發者有注意到 .h 並非目標產生的問題),上面提到那一大串 sed ,如果是用 gcc 加上這個選項就不需要了
  • -MF 設定輸出到的檔案,相當於 redirect >
POSTCOMPILE 則把暫時的dependency file .Td 改名為.d,如果沒有要對 .Td 做什麼,拿掉這行,指定 -MF 直接寫到 .d 檔也是不錯的選擇。

第二段在.o 的dependency 加上 .d,command 編譯 .o 檔,同時gcc 產生.d 檔。
第三段寫入空的 .d 檔目標,以便處理 .d 檔被刪掉的狀況,.precious 讓 .d 檔在Makefile 當掉或被砍掉的時候不會被刪掉;最後 include 產生出來的 .d 檔。

大概的解法就是這樣,其實,如果project 很小的話,根本就不用這麼大費周章,像下面這個解法,直接把所有SRC送給gcc 產生dependency ,寫到 .depend 檔案,然後每次都include .depend,對 99.9%的project 來說應該都足夠了:
http://stackoverflow.com/questions/2394609/makefile-header-dependencies

2017年2月24日 星期五

使用dbench 進行硬碟效能測試

最近遇到需要大量進行儲存系統讀寫的要求,因為包含了samba 硬碟,查了一下發現了dbench 這個測試程式,就試用了一下,它支援本機的測試,也支援samba, iscsi跟nfs 測試:

本測試需安裝 dbench 進行測試,在Linux 主機上使用下列指令取得dbench。
git clone https://github.com/sahlberg/dbench

先安裝要測試網路硬碟所需的library:
sudo apt-get install libiscsi-dev
sudo apt-get install smbclient
sudo apt-get install libsmbclient-dev
sudo apt-get install samba-dev
sudo apt-get install libnfs-dev
在dbench 中使用下列指令編譯,configure 要加的參數來自這個gist,才能找到samba client library:
https://gist.github.com/Labisana/6d94b7db13b08be586ce
./autogen.sh
./configure CFLAGS="-I/usr/include/samba-4.0/"
make
make install

完成編譯dbench,在測試機上,可以使用dbench來進行測試,簡單的如:
./dbench --loadfile=loadfiles/client.txt -t <TIME> <THREAD>
或是想要測試遠端的samba server:
./dbench -B smb --smb-share=//<IP>/<DIR> --smb-user=<USER>%<PASS> --loadfile=loadfiles/smb_1.txt -t <TIME> <THREAD>

各欄位說明如下:
<IP> <DIR>:samba之IP位址及資料夾名稱。
<DIR> <USER> <PASS>:samba使用者之帳號與密碼。
<TIME>:測試時間
<THREAD>:使用多少程序進行測試。
如果是nfs 或iscsi 的話,應該會需要其他的參數以設定登入,不過手邊沒有nfs 或iscsi 可以測試,只好先跳過。

loadfile 是dbench 的測試檔案,裡面可以描述想要dbench 執行的讀寫動作,例如開檔、寫檔等等,如果寫得好,它應該可以重現一般使用者真實的讀寫狀況,不過我都直接用它在loadfiles 資料夾中預設的檔案,如上面的smb_1.txt。
自己試過在超過50 個thread 的時候,samba很容易出現寫入錯誤,所以保守一點就用50 個thread 就是了,如果是本機測試的話,就不用這麼保守;不過話說回來一般本機上也不會有這麼多人一起用你的電腦就是了。
執行之後,dbench 就會印出測試的報表:
Operation                Count    AvgLat    MaxLat
--------------------------------------------------
Flush                      849   117.384   207.657
Close                     9000     0.003     0.117
LockX                       40     0.006     0.019
Rename                     520     0.078     3.887
ReadX                    19240     0.007     1.723
WriteX                    6039     0.056    11.209
Unlink                    2480     0.058     3.291
UnlockX                     40     0.003     0.005
FIND_FIRST                4300     0.035     0.162
SET_FILE_INFORMATION       990     0.090    10.150
QUERY_FILE_INFORMATION    1940     0.002     0.017
QUERY_PATH_INFORMATION   11350     0.013     8.822
QUERY_FS_INFORMATION      2040     0.004     0.048
NTCreateX                12260     0.020     8.160

Throughput 42.255 MB/sec  10 clients  10 procs  max_latency=207.669 ms

參考資料:
http://kongll.github.io/2015/04/24/dbench/
https://dbench.samba.org/doc/dbench.1.html

2017年2月14日 星期二

在Archlinux 上安裝mariadb筆記 (Install mariadb on Archlinux)

最近在我的Archlinux 上面安裝MySQL,結果撞了一堆牆,在這裡筆記一下過程,希望有機會的話能幫到其他使用者。
步驟就是照著wiki 所講,一步一步往下做:
https://wiki.archlinux.org/index.php/MySQL

首先因為Archlinux 已經改用mariadb ,MySQL 移去AUR,不過我試過,用yaourt -S mysql 也會裝mariadb ,一個由不得你的概念。
sudo pacman -S mariadb

使用mysql_install_db 設定好/var/lib/mysql
mysql_install_db --user=mysql --basedir=/usr --datadir=/var/lib/mysql

用systemd 啟動mariadb service
systemctl start mariadb
最後可以用mysql_secure_installation來進行安全設定

我在start mariadb 這步遇上問題,service 總是起不來,出現類似這樣的訊息,要不就是start mariadb 直接hang住:
Job for mariadb.service failed because the control process exited with error code.
See "systemctl status mariadb.service" and "journalctl -xe" for details."
使用systemctl status mariadb.service 之後,它會列出哪行指令出了錯,我記得沒錯的話是 /usr/sbin/mysqld,總之它開不了 /var/lib/mysql 中的某些檔案。
後來發現原因是,在安裝mariadb 的時候,理論上它要加上mysql 這個使用者,但原因不明的沒有加上去,因此我們要手動幫它補上:
groupadd -g 89 mysql
useradd -u 89 -g mysql -d /var/lib/mysql -s /bin/false mysql
chown mysql:mysql /var/lib/mysql

然後重跑上面的mysql_install_db,就能順利的把mariadb 跑起來了

2016年11月9日 星期三

整理一些增進效率的shell 工具

最近整理了一下自己工作上有在用的一些shell 工具或指令,在這裡分享一下,也許能讓大家工作更有效率。

shell:

沒錯不要懷疑,就是shell,如下這篇shell 本身就有不少快捷鍵可用:
https://blog.longwin.com.tw/2006/09/bash_hot_key_2006/
不過其實不少快捷鍵都有更好記的單鍵可代替,例如到行首尾可以用Home 跟End,平常比較常會用到的其實只有:

  • Ctrl + W 刪除一個詞
  • Ctrl + R 後打關鍵字,可以找上一個拿關鍵字的命令,持續Ctrl + R 持續找,光是用Ctrl + R 就海放只用↑↓在找指令的了(X,不過用了下面的fzf ,它會用fzf 代替單純的 Ctrl + R搜尋
  • Ctrl + L 可以清除畫面,跟下clear 是一樣的效果,而且clear 打起來超級快,最近還在習慣用Ctrl+L;我覺得這個重要的是在gdb 裡面沒有clear 指令,要用Ctrl+L來清除畫面。

autojump

本指令感謝AZ 大神的推薦,一個記錄你cd 過的地方,以後可以直接jump 過去的指令
https://github.com/wting/autojump
主流的linux 都有套件支援了,安裝後在shellrc 裡面source 它即可
if [[ -e /usr/share/autojump/autojump.zsh ]]; then
  source /usr/share/autojump/autojump.zsh
fi
接著它需要一點時間train 一下,平常cd 的時候它就會把cd 的資料夾記下來,之後就能用
j foo
jo foo
前者cd到名稱含有或近似foo 的資料夾,後者直接開視窗的file manager檢視該資料夾。
也可以下多個參數,例如:
victim/alice/data
victim/bob/data
如果只下j data 可能會跳去alice的資料,但下了 j bob data 就能跳到後者
我記得我在寫minecraft plugin 的時候,差點沒被java 氣死,為啥source code 一定要藏這麼多層勒,光cd ../../../ 就累死了,那時沒用autojump 深感相見恨晚;後來寫一些包含不少小專案,還要source sdk 的android project ,這個指令也幫了大忙。

fzf

本指令感謝士銘大神推薦,fzf 是一個模糊搜尋工具,讓你列出很多的東西,然後模糊查找目標:
https://github.com/junegunn/fzf
目前還在習慣中,安裝的話透過它github 頁面上的script 安裝即可:
git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
~/.fzf/install
archlinux 則有收到repository 中,安裝repository 之後在shellrc 裡面加上
source /usr/share/fzf/completion.zsh
source /usr/share/fzf/key-bindings.zsh
最一般的使用方式,是把其他會吐出一堆東西的指令當作fzf 指令的輸入,fzf 會把輸出全列出來,然後讓你打字進行模糊搜尋,或者用上下選取,用一下就覺得快若閃電,絲毫沒有延遲,例如:
locate servo | fzf
find -type f | fzf
加了key-bindings.zsh 之後就連上面的Ctrl+R 也會變成fzf 模式,潮爽der;fzf 也有跟vim 綁定,不過這部分我沒試用,暫時先持保留意見。

2016年10月9日 星期日

進行arm 開發時遇到的各種狀況

約一週前重灌了電腦,重灌這檔事最麻煩的就是一些開發環境會消失不見,平常用得順手的東西突然不見了,例如我的arm 開發環境就是一例:
首先在archlinux 上面,跟之前這篇不同,現在只剩下gcc49, gcc53跟gdb 還在
http://yodalee.blogspot.tw/2015/04/llvmclang.html
arm-none-eabi-gcc53-linaro
arm-none-eabi-gdb-linaro

替代品是AUR中下面這些套件(順帶一提,安裝這幾個套件之前,請先去/etc/makepkg.conf 把MAKEFLAGS 改成 -jn,不然用單核心編譯這幾個近100 MB的程式會編譯到想翻桌):
arm-linux-gnueabihf-binutils
arm-linux-gnueabihf-gcc
arm-linux-gnueabihf-glibc
arm-linux-gnueabihf-linux-api-headers
arm-linux-gnueabihf-gdb

安裝完之後當然就來用一下了,測試當然就用最簡單的helloworld.c 去測:
$ arm-linux-gnueabihf-gcc helloworld.c -o hello
exec format error: ./hello
這是當然的囉,因為我的機器是x86_64,而arm-gcc 編出來的執行檔要在arm 架構上執行,幸好這年頭我們有qemu-arm 可以用(老實說,悲劇就是從這裡開始的= =):
qemu-arm hello
/lib/ld-linux-armhf.so.3: No such file or directory
這又是為什麼呢?我們可以file 它一下,可以看到它的interpreter 是/lib/ld-linux-armhf.so.3,而這個檔案是不存在的。
file hello
hello: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 2.6.32, BuildID[sha1]=716a92a4985090baa83f8b762c5f9844e197ed83, not stripped

它真正的位置在arm-gcc 的安裝位置:/usr/arm-linux-gnueabihf/lib,創個symbolic link過去
ln -s /usr/arm-linux-gnueabihf/lib/ld-linux-armhf.so.3 /lib/ld-linux-armhf.so.3
ll ld-linux-armhf.so.3
lrwxrwxrwx 1 root root 48 Oct 5 19:10 ld-linux-armhf.so.3 -> /usr/arm-linux-gnueabihf/lib/ld-linux-armhf.so.3

這時候就不會有No such file or directory,雖然會換成另一個錯誤
hello: error while loading shared libraries: libc.so.6: wrong ELF class: ELFCLASS64 http://blog.csdn.net/sahusoft/article/details/7388617
原因是dynamic linker 在連結動態函式庫時,找到的libc.so.6 是錯誤的格式,它找不到它要的arm 格式的函式庫,這步有時會有其他的錯誤像是:
cannot open shared object file: No such file or directory
這就是電腦上沒有安裝相對應的函式庫,可以用ldd 來確認這件事,沒有就要安裝該函式庫;或者函式庫安裝在/usr/lib 以外的特殊路徑,就要利用ld.so.conf去設定,像我的/etc/ld.so.conf.d裡面就有:
android-sdk.conf
cuda.conf
fakeroot.conf
ffmpeg2.8.conf
lib32-glibc.conf
octave.conf
openmpi.conf

不過我們的狀況比較複雜一點,在這之前試著把 /usr/arm-linux-gnueabihf/lib 加到ld.so.conf 裡面,執行ldconfig 時就出問題了
$ ldconfig
ldconfig: /usr/lib/ld-linux-armhf.so.3 is for unknown machine 40.
我們x86_64 的ldconfig 壓根不認arm 的函式庫,這些函式庫也沒加到ld.conf.cache,自然也不會在執行時被 ld-linux-armhf.so.3 連結,所以上面的執行還是失敗了。

所以說了這麼多,我們到底要怎麼樣才能把我們的hello world 跑起來?

首先是rpath
http://stackoverflow.com/questions/2728552/how-to-link-to-a-different-libc-file
在呼叫gcc 的時候使用
arm-linux-gnueabihf-gcc -Xlinker -rpath=/usr/arm-linux-gnueabihf/lib hello.c
這樣編譯出來的執行檔就會以/usr/arm-linux-gnueabihf/lib 為第一優先,就能夠直接以qemu-arm 去執行。

另一個方法是設定LD_LIBRARY_PATH,這個的優先順序高過 ldconfig 設定的路徑,也能讓qemu-arm 跑起來:
export LD_LIBRARY_PATH=/usr/arm-linux-gnueabihf/lib/
當然這不是一個好方法,也是老話題了,請見:
http://xahlee.info/UnixResource_dir/_/ldpath.html

或者是利用qemu-arm 的-L flag,這個的位階低於LD_LIBRARY_PATH,因此用這個要確定LD_LIBRARY_PATH沒有設值。
qemu-arm -L /usr/arm-linux-gnueabihf hello

試了這麼多方法,最後一種其實才是最有效的做法,也是我忘掉的方法(yay

寫文的同時我大概整理一下 man ld 裡面,在選項-rpath=dir跟-rpath-link=dir的說明:
首先是rpath ,這是設定runtime 時期的library search path,這些字串會完整的被複製到runtime linker (也就是ld.so)用來尋找共享函式庫;如果rpath 沒有設定,則linker 會使用LD_RUN_PATH 的值。
因此透過rpath 我們有兩招:
第一個是上面寫的,用rpath 設定搜尋路徑:
arm-linux-gnueabihf-gcc -Xlinker -rpath=/usr/arm-linux-gnueabihf/lib hello.c
arm-linux-gnueabihf-gcc -Wl,-rpath=/usr/arm-linux-gnueabihf/lib hello.c

第二個是直接編譯,但先設定LD_RUN_PATH的值:
export LD_RUN_PATH=/usr/arm-linux/gnueabihf/lib
arm-linux-gnueabihf-gcc hello.c

我們可以用readelf -d 把dynamic sections 給讀出來,就能看到我們設定的rpath 了:
readelf -d hello
0x0000000f (RPATH) 函式庫路徑:[/usr/arm-linux-gnueabihf/lib]

另外還有一種是 -L,它會設定連接時搜尋共享函式庫的目錄,這裡只給一個最粗淺的例子:
arm-linux-gnueabihf-gcc -c hello.c -o hello.o
arm-linux-gnueabihf-ld hell.o -o hello
會發生undefined reference to puts 的錯誤,因為我們沒有連接所需要的 c library,另外我們也沒有指定程式的進入點為何,要能連結通過至少要:
arm-linux-gnueabihf-ld hell.o -o -lc -L/usr/arm-linux-gnueabihf/lib hello –entry main

當然這樣不代表可以執行,試著執行會發現dynamic linker 並沒有正確設定,除此之外還有各種runtime 的library 需要連結進去才會動;要看到可運作的呼叫方式,可以用gcc -v (-verbose) 來觀察。

-rpath-link 則只指定link 時搜尋shared library的路徑,這個路徑不會包含到executable 裡面,這個我一時之間給不出例子,但在這裡有找到,利用 -rpath-link=. 在編譯時指定在編譯時目錄尋找 shared library,另外關鍵的差別都寫在這幾句話了:
在 gcc 中使用 rpath/rpath-link 指定 shared library 搜尋路徑
在 -rpath-link 裡指定 "." (當前目錄) 還算正常,因為我們可以控制現在的工作目錄,
但是在 -rpath 裡指定 "." 就有點奇怪,因為你不知道別人會在哪個目錄執行你的程式...

使用-rpath-link 須知它也會蓋掉原本的搜尋路徑,因此用-rpath-link有個危害是:link time linker(ld)跟runtime linker (ld.so) 可能會使用不同的shared library,因為後者並沒有設定這個路徑,而是去預設的路徑尋找。

man ld 有列出 link time linker 的搜尋路徑,不過它未寫明有沒有先後關係,但以-rpath 來說顯然是有的:
https://my.oschina.net/shelllife/blog/115958
http://blog.lxgcc.net/?tag=dt_runpath
1. 透過 -rpath-link 指定的資料夾
2. 透過 -rpath 指定的路徑,如上所說,這跟 -rpath-link的差別是它會被包括到runtime linker中
3. 如果前兩者都沒有指定,尋找LD_RUN_PATH環境變數的值
4. 在SunOS,如果 -rpath 未指定,尋找 -L 選項設定的值
5. 尋找LD_LIBRARY_PATH 設定之路徑,這裡有句 For a native linker,不確定native linker是在指什麼特別的linker
6. For a native ELF linker,會查找現在引入的共享函式庫中設定的DT_RUNPATH 跟DT_RPATH section,後者的優先度高於前者(其實這就是在編譯shared library 時,以rpath 編進去的路徑:)
7. 預設路徑 /lib, /usr/lib
8. /etc/ld.so.conf 設定的路徑、

整體大概就是這樣,從編譯到執行,有許許多多的地方都能讓我們有各種方法對執行檔「上下其手」,去改動裡面連結、動態連結引入的東西
不過說老實話,其實這篇文…是呃…我忘了 qemu-arm 使用上就是用 -L 去指定library path 的產物,花了我一堆時間… 這真的是:Hello World 年年都會寫,每一年都有不同的體會呢XD。

2016年10月2日 星期日

使用網路線直接連線進行資料傳輸

這篇講的其實不是什麼大不了的事情
總之先前重灌電腦的時候,筆電裡面的平常放著不少動畫檔,為了求備份方便(時間與空間考量)我沒將它們備份到隨身硬碟上,而是把它們全部刪掉了。
反正我桌機上還有同樣的動畫資料夾,想說重灌過後再把它們複製一份就是了。

在複製一份的時候,試了一下用網路線的方式來傳檔案,查一些文件,有說2002 之後的網路卡,配合網路跳線都能連得上,也有相關文章在做這件事,於是就來試試看:
http://askubuntu.com/questions/22835/how-to-network-two-ubuntu-computers-using-ethernet-without-a-router
我有一台筆電跟一台桌電,大致的設定如下: 首先是在兩端都接上網路線,分別在筆電和桌電的eth0(也可能是其他名字,反正就乙太網路介面)上設定不同的static ip:
我筆電設定為:192.168.66.99/24 gateway:192.168.66.254
桌電設定為:192.168.66.100/24 gateway:192.168.66.254

在筆電上用ifconfig 就會看到介面ip 已設定:
enp2s0f0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
  inet 192.168.66.99 netmask 255.255.255.0 broadcast 192.168.66.255

連接上網路線的話,也會看到雙方的有線網路都顯示連線,這時候可以先用ping 去測一下,確定已連線
ping 192.168.66.100 -c 3
PING 192.168.66.100 (192.168.66.100) 56(84) bytes of data.
64 bytes from 192.168.66.100: icmp_seq=1 ttl=64 time=12.4 ms
64 bytes from 192.168.66.100: icmp_seq=2 ttl=64 time=7.74 ms
64 bytes from 192.168.66.100: icmp_seq=3 ttl=64 time=2.46 ms

--- 192.168.66.100 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 2.468/7.566/12.488/4.092 ms

接著在筆電上設定ssh,我在archlinux 上是用openSSH,相關的設定就參考: http://smalldd.pixnet.net/blog/post/24627330-arch-linux-%E5%AE%89%E8%A3%9D-openssh
最後開啟服務:
systemctl start sshd
就能透過該ip ,從桌電ssh 進入筆電了:
ssh username@192.168.66.99

確認連線之後,就能用任何你想得到的方法,透過網路備份,像什麼sftp, fillzilla 的,我最後同樣用rsync 來處理,這應該會要求目標電腦上也有安裝rsync,它會透過一個rsync --server來接收檔案:
rsync -av --progress -e ssh /media/data/Animate 192.168.66.99:/media/data
log 我就不貼了,反正就一直傳一直傳一直傳,均速大概是 50 MB/s,雖然連線資訊寫的速度是1000 Mb/s,不過也算相當快了,一集動畫100M 在1-2 秒間就會傳完,我自己用隨身硬碟的經驗是,雖然usb2.0 速度理論上有 30 MB/s,3.0 當然更快,但是實際在複製的時候,通常都達不到這麼快,甚至有時會降到3-5 MB/s,這點我懷疑有可能是我隨身硬碟用的格式是ntfs,而linux 上的driver ntfs-3g 有時效能不太好,相對來說網路傳輸反而穩定得多。
另一方面,隨身硬碟備份要來回複製跟貼上,不像rsync按下去就行了;相對網路讀寫能同時進行就可以一次複製完,外接硬碟再怎麼快,讀跟寫還是分開,外接硬碟要能相匹敵,讀寫均速至少要是網路速度的兩倍才行,問題是現下可能連網路的1/5 都不一定達得到,網路的優勢實在太過顯著。

附一張連線的實際照片,隱約可見的藍色線就是網路線,桌電遠端進入筆電,正在用rsync 上傳資料: 

2016年9月27日 星期二

筆電重灌全記錄

最近家目錄 100 GB 的硬碟被我塞滿了。

雖然後來刪掉一些東西,例如之前玩一些<資料>分析contest 的資料,還有之前玩虛擬貨幣primecoin載了整個區塊鏈,也全刪掉;清了大概30 GB 的空間出來,不過覺得還是不夠好,畢竟現在的電腦從2013年4月用到現在,硬碟分割不是很好,有太多長期留下來的東西,設定檔愈來愈亂,空間分配也不是很好;覺得是時候重灌系統調整體質,以迎接下個十年,逆風高灰(X。

重灌系統,麻煩的就是要先備份系統,我選擇的是把家目錄備份出來,備份使用rsync,用rsync 的好處是快,而且可以增量備份,隨時加一點東西上去也OK,例如週末有空的時候做全資料夾備份,直到重灌前一刻再增量備份:
http://newsletter.ascc.sinica.edu.tw/news/read_news.php?nid=1742
rsync -av --exclude=”.*” --exclude=”.*/” src_dir dest_dir
不過如果你的資料夾裡有 git 的話,就要小心,上面這個不會備份隱藏的資料夾,要把那個exclude 拿掉。

硬碟分割的部分,這次決定採用激進的分割方式,雖然電腦上會保留Windows…用來……打遊戲,不過這次不分割所謂的D槽給Windows,資料分割區從NTFS 改為ext4。

以下是這次的分割狀況,沒特別註明就是GB:
  • /boot 200 MB
  • windows 150 之前的windows 被塞了不少acer 預設的程式在裡面,這回全部清得乾乾淨淨,給150 應該很夠用了。
  • Linux root 75,現在是給50 GB,我也從來沒想過它會裝滿,直到有一天要燒FPGA裝了Xilinx Vivado,瞬間root 剩下零頭,每回開機都會警告空間不足…
  • Linux /var 30 曾看archlinux 的wiki 頁面分12 G 給它,結果安裝一堆套件之後立刻就滿了
  • Linux /home 200 一口氣倍增到200 G,應該絕對夠用了吧
  • Linux /data 硬碟剩下的空間大概500 G就全分給data,之前用NTFS 現在改用ext4,用來放一些像是東方啦動畫之類的東西。

另外備份所有已裝套件:archlinux 使用pacman -Q > allpackage 或 yaourt -Q > allpackage 把所有安裝的套件包都列出來

然後就可以放心的把磁碟區全部clean 掉,然後先裝windows,再裝我的不敗開發環境:archlinux;颱風天一個人躲在房間,任窗外狂風暴雨,我的電腦也是狂風暴雨XD。不過我太久沒裝了,還是參考一下強者我同學的安裝記錄,大體上沒什麼不同,我是選用mate 作為我的桌面環境:
http://johnjohnlys.blogspot.tw/2016/06/archlinux.html
小試一下,我的archlinux就回來啦,重裝套件的部分,就用剛剛上面的reinstall 文件,搭配vimdiff,很快就能把套件全部裝回來了…才怪。

老實說,因為選用的是archlinux 的關係,有不少比較不那麼正式卻又很常用到的東西,都要從AUR 去裝;而AUR 跟pacman相比就是很吃網路跟編譯時間,結果我整個裝下來,花的時間不比windows 的少,雖然說爽度還是有差,AUR 只要yaourt 裝好,指令打下去就是了;windows 還要自己去找安裝檔,用滑鼠一個一個安裝,只能說重灌就是麻煩……希望這次轉骨完之後,下次可以等到很久很久之後再重灌了。

ps. 後來發現在 /etc/makepkg 裡面的 -j 選項沒有開,等於是一直用單核心編譯,不知道速度上能差到多少

2016年9月20日 星期二

在Archlinux 安裝arduino 開發環境

最近開始接觸arduino開發板
在archlinux 上開發arduino 也相當簡單,照著wiki上面做便是:
https://wiki.archlinux.org/index.php/arduino
先用yaourt 裝arduino
然後把一般使用者加到uucp 跟lock 群組裡面,讓使用者可以取用serial 的權限:
# gpasswd -a $USER uucp
# gpasswd -a $USER lock
再來登出登入就設定完了,打開arduino 就有介面可以使用了。

另外有遇到一個問題,因為手上拿到的arduino 使用的是Intel Edison的板子,因此打開arduino 之後,要用它的board manager 或中譯板子管理員安裝Intel Edison 的板子。
而安裝時會出問題,大致的錯誤訊息會類似這樣:
Setting it up...find: invalid mode ‘+111’
/tmp/tmp.ioGoYaYhZu/relocate_sdk.sh /home/yodalee/.arduino15/packages/Intel/tools/core2-32-poky-linux/1.6.2+1.0/i686/relocate_sdk.sh
SDK could not be set up. Relocate script failed. Abort!

然後如果不理它直接去arduino 裡面編譯看看的話,會出現類似這樣的錯誤:
$HOME/.arduino15/packages/Intel/tools/core2-32-poky-linux/1.6.2+1.0/i686/sysroots/x86_64-pokysdk-linux/usr/bin/i586-poky-linux/i586-poky-linux-g++: No such file or directory

總之在家目錄的超深的地方,某個g++ 檔案找不到,但如果你去它所寫的路徑看一下,它確確實實的就在那裡,直接在shell 下執行會得到一樣的結果。
第一次遇到這個問題是在管工作站的時候,某個EDA 公司給的執行檔,也是怎麼跑都跑不起來,當下完全就是見鬼了,檔案就在那裡linux 你是跟我開玩笑嗎?後來那個EDA 好像是給錯版本,忘了是32 bits 的工作站給了64 bits 執行檔,還是64 bits 工作站給了32 bits 執行檔,總之後來強者我同事解掉了。

解法一(不完整,請用解法二)
如果用file 去看那個執行檔就會看出一點端倪,裡面有這句:
interpreter /opt/poky-edison/1.6.1/sysroots/core2-32-poky-linux/lib/ld-linux.so.2
意即這個執行檔需要這個dynamic linker來執行,而安裝時,這個路徑並沒弄好,因為在這個安裝包($HOME/.arduino/packages/Intel/tools/core2-32-poky-linux/1.6.2+1.0/i686)下面有找到這個檔案sysroots/core2-32-poky-linux/lib/ld-linux.so.2
因此解法就是在opt下面,用softlink連結1.6.1到i686 下面,編譯就能跑得起來了。
上述工作站的問題也是類似,大概是忘了裝32/64 bits 的libc,在ubuntu的話應該要補裝libc6-i386 lib32stdc++6 等。

解法二:
這個才是正常的解法,在i686 資料夾下,會找到安裝時執行的script:install_script.sh,實際執行的話也會報錯,它在呼叫relocate_sdk.sh裡面執行relocate_sdk.py後會出錯,原因是給relocate_sdk.py 參數太少了。
後來查到真正的原因跟解法:
http://askubuntu.com/questions/764715/unable-to-install-intel-i586-library-intel-galileo-gen-2-in-arduino-ide-on-ubu

把install_script.sh 裡面的
executable_files=$($SUDO_EXEC find "$native_sysroot" -type f -perm +111 -exec printf "\"%s\" " {} \; )
的+111 改成 /111就行了,就是find 出錯導致executable_files 沒找到檔案,relocate_sdk.sh 就沒把該修改interpreter 的執行檔傳給relocate_sdk.py,最後就變成interpreter 還指向/opt 的狀況。

確實去找manpage find,會找到這行
-perm +mode
This is no longer supported (and has been deprecated since 2005). Use -perm /mode instead.
所以大概是這個script 太久沒維護了吧。

後話:
現在筆電的archlinux 自從我2013/4/24 10:26:04 灌好之後,經歷了無數程式開發,無論系統程式、機器學習、Android、Arduino,從來沒讓我失望過,archlinux果然是不敗開發神機。
至於我怎麼知道這個時間,是參考這個:
http://unix.stackexchange.com/questions/9971/how-do-i-find-how-long-ago-a-linux-system-was-installed
用tune2fs 去偵測例如root 或boot 分割區的創立時間,大致就是系統灌好的時間了。

2016年9月16日 星期五

不為人知的gdb 使用方式系統-gdb pretty printer auto load

前言:
最近因為jserv 大神的關係,看了下面這部Become a GDB Power User
https://www.youtube.com/watch?v=713ay4bZUrw
覺得裡面還不少生猛的用法之前都不會用,決定把它整理一下,寫個系列文:

上回我們提到了gdb 的pretty printer,現在我們就來看個範例:寫Rust 用的Rust-gdb。

其實Rust-gdb 跟gdb 本質上沒什麼不同,裡面將python pretty printer 的路徑加到gdb source 的路徑,然後執行gdb…
gdb -d "$GDB_PYTHON_MODULE_DIRECTORY" \
-iex "add-auto-load-safe-path $GDB_PYTHON_MODULE_DIRECTORY" \
"$@"
這樣感覺什麼都沒加進去呀,事實上,rust 是透過比較隱晦的機制,在編譯的時候將要引入的資料插在執行檔的 .debug_gdb_scripts 裡面,這個section 會包含null-terminated 的條目(entry)指明gdb 要載入哪些script,每個script 的開頭會有一個byte 指明這是哪一種entry,拿我之前寫的racer 來試試,把它的section 印出來:
$ readelf -S racer
[17] .debug_gdb_script PROGBITS         0000000000743af8  00743af8
     0000000000000022  0000000000000000 AMS       0     0     1
然後我們把這個區段給印出來:
$ readelf -x .debug_gdb_scripts racer
「.debug_gdb_scripts」區段的十六進位傾印:
0x00743af8 01676462 5f6c6f61 645f7275 73745f70 .gdb_load_rust_p
0x00743b08 72657474 795f7072 696e7465 72732e70 retty_printers.p
0x00743b18 7900                                y.

開頭的01,指明它是在script in python file,內容則指明要載入gdb_load_rust_pretty_printers.py,一般我覺得是用不著這麼搞工啦,畢竟要用這招就要在編譯器上面動手腳,大概在自幹語言的時候用上這招比較方便,相關文件可見這兩個連結:
https://sourceware.org/gdb/onlinedocs/gdb/Auto_002dloading-extensions.html#Auto_002dloading-extensions
https://sourceware.org/gdb/onlinedocs/gdb/dotdebug_005fgdb_005fscripts-section.html#dotdebug_005fgdb_005fscripts-section

rust-gdb 所用的方法,如這份文件str_lookup_function之後的部分所示:
https://sourceware.org/gdb/onlinedocs/gdb/Writing-a-Pretty_002dPrinter.html#Writing-a-Pretty_002dPrinter

把pretty printer 寫完之後,首先要先寫一個gdb 的lookup_function,這個function 會負責回傳適當的pretty printer,不然就回傳None。
文件建議這個pretty printer 放到單獨的python package 裡面,如果這個pretty_printer是針對某個函式庫寫的,那更建議package 的名字要加上函式庫的版本號,這樣gdb 能載入不同版本的pretty printer;最後在auto load 的script 中import 這個pretty printer,用gdb.pretty_printers.append ,把這個lookup_function插入gdb的objfile 中即可。

Rust-gdb 就是這樣做的:
auto load 的script gdb_load_rust_pretty_printers.py 中,引入gdb_rust_pretty_printing,然後呼叫它的register_printers,把rust_pretty_printer_lookup_function插入目前的objfile 中。
一個lookup_function 會接受一個gdb Value,我們可以從中取得它的type的tag,從tag 中辨別它是什麼型別的資料,藉此回傳適當的pretty printer。

我用之前的QString 做了個簡單的範例,首先我們要改造一下我們的qstring.py,加上lookup function 跟register_printer,lookup function 在型別為QString 時會將這個值傳入我們寫的QStringPrinter,並回傳它:
def qstring_lookup_function(val):
  lookup_tag = val.type.tag
  if lookup_tag is None:
    return None
  regex = re.compile("^QString$")
  if regex.match(lookup_tag):
    return QStringPrinter(val)
  return None

def register_printers(objfile):
  objfile.pretty_printers.append(qstring_lookup_function)
接著我們寫一個autoload script : hello-gdb.py,雖然我們用到了gdb,但gdb 內的python 會自動引入這個gdb 這個module,所以不引入也沒關係:
import qstring
qstring.register_printers(gdb.current_objfile())
另外要注意的是,一定要使用自動載入的方式,因為gdb.current_objfile() 只有自動載入的時候才會設定為當前的objfile。
之所以叫hello-gdb.py,是因為我們採用另一個機制:objfile-gdb.py的檔案會被自動載入,我執行檔名叫hello ,hello-gdb.py會在gdb 開啟時自動載入,其他有用到的dynamic library ,檔名叫libXXX-gdb.py也可以用類似的方法載入。
https://sourceware.org/gdb/onlinedocs/gdb/objfile_002dgdbdotext-file.html#objfile_002dgdbdotext-file

當然故事不是這樣就完了,首先hello-gdb.py會用到qstring.py,所以我們必須把qstring的位置加到PYTHONPATH 變數之中;第二gdb 不會隨意自動載入第三方的script,以免造成危害(雖然說到底能有啥危害我也不知道……),因此我們必須在gdb 開始自動載入script 前,把這個script 加到安全名單之中;因此,我們的gdb 呼叫會變成下列兩者擇一,也就跟rust-gdb 相差無幾了:
PYTHONPATH="$PYTHONPATH:/tmp" gdb -iex "set auto-load safe-path /tmp" hello
PYTHONPATH="$PYTHONPATH:/tmp" gdb -iex "add-auto-load-safe-path /tmp/hello-gdb.py" hello

大概的故事就是這樣了,Rust-gdb 當然是lookup function 那邊搞了很多,把型別什麼共同的部分獨立出來,以便支援gdb 跟lldb。
一般的lookup function 就不用寫這麼多了。
又說回來,要寫到大型的lookup function 應該也只有開發語言跟函式庫的時候需要,一般人更用不到這個功能,所以這篇比起上篇其實更是廢文一篇(蓋章,感謝大家又貢獻兩分鐘的時間給小弟的blog (炸。

2016年9月14日 星期三

不為人知的gdb 使用方式系統-gdb pretty printer

前言:
最近因為jserv 大神的關係,看了下面這部Become a GDB Power User
https://www.youtube.com/watch?v=713ay4bZUrw
覺得裡面還不少生猛的用法之前都不會用,決定把它整理一下,寫個系列文。

這篇來介紹 gdb 的pretty printer:
https://sourceware.org/gdb/onlinedocs/gdb/Pretty-Printing.html#Pretty-Printing

假設我們有一個客製的struct 或是class,例如Qt4 的QString,如果我們用原本gdb 的print 去印出QString的話:
QString s("Hello World");
(gdb) p s
$2 = {static null = {<No data fields>}, static shared_null = {ref = {_q_value = 2}, alloc = 0, size = 0, data = 0x7ffff7dd70fa <QString::shared_null+26>, clean = 0,
simpletext = 0, righttoleft = 0, asciiCache = 0, capacity = 0, reserved = 0, array = {0}}, static shared_empty = {ref = {_q_value = 1}, alloc = 0, size = 0,
data = 0x7ffff7dd70da <QString::shared_empty+26>, clean = 0, simpletext = 0, righttoleft = 0, asciiCache = 0, capacity = 0, reserved = 0, array = {0}},
d = 0x603dc0, static codecForCStrings = 0x0}

因為QString 是個結構的關係,我們無法單純印個char array ,反而會印出裡面所有的資訊,它真的資訊是存在那個 d 裡面,要真的印字串就要從那個d 裡面去印。
之前在寫Qt 專案的時候有遇到類似的問題,那時有查到一個printqstring 的自訂函式,把下面這段加到 ~/.gdbinit 裡面:
define printqstring
  printf "(QString)0x%x (length=%i): \"",&$arg0,$arg0.d->size
  set $i=0
  while $i < $arg0.d->size
    set $c=$arg0.d->data[$i++]
    if $c < 32 || $c > 127 
      printf "\\u0x%04x", $c
    else
      printf "%c", (char)$c
    end 
  end
  printf "\"\n"
end

就可以在debug 時使用printqstring來印出QString
(gdb) printqstring s
(QString)0xffffe650 (length=12): "Hello World!"

當然我們可以用gdb 的pretty printer 來做到類似的事,而且不需要自訂函式,單純用p s 也能做到一樣的效果。
gdb v7 開始加入對python的支援,可以讓我們透過python和gdb 互動,在python 之中import gdb 之後,就可以利用一連串定義完善的介面去跟gdb 互動,詳細上用法還不少,這裡我們只先試著寫個pretty_printer。
Python 的pretty printer 就是實作一個class,至少要實作__init__跟to_string() 兩個介面,其他的函式可以參考:
https://sourceware.org/gdb/onlinedocs/gdb/Pretty-Printing-API.html#Pretty-Printing-API
之後使用gdb.printing的RegexpCollectionPrettyPrinter,產生一個叫QString 的printer,利用add_printer,只要型態名稱符合^QString$ 的物件,就用QStringPrinter來處理,最後用regeister_pretty_printer把這個RegexpCollectionPrettyPrinter 塞進去:
# qstring.py
import gdb.printing

class QStringPrinter(object):
  def __init__(self, val):
    self.val = val
  def to_string(self):
    return "I'm Qstring"
  def display_hint(self):
    return 'string'

pp = gdb.printing.RegexpCollectionPrettyPrinter('QString')
pp.add_printer('QString', '^QString$', QStringPrinter)
gdb.printing.register_pretty_printer(gdb.current_objfile(), pp)
進到gdb 之後,首先source qstring.py ,接著去print 一個QString 的話,gdb 就會呼叫我們寫的QStringPrinter 的to_string 來處理,就會得到垃圾訊息:I'm QString。

to_string 回傳的規則如下:
python 的integer, float, boolean, string 都是gdb 可處理的(可轉換為gdb.Value),會由gdb 直接印出;回傳None的話則不會印東西。
傳回其他的gdb.Value,那gdb 會接續處理這個value,處理中也會呼叫其他註冊的pretty printer;真的無法處理,gdb就會丟出exception。
另外我們可以實作display_hint 的介面,告知gdb 要如何處理這個值的輸出,這會影響到gdb 如何接續處理這個值;這裡我們表示這個輸出是個字串。

上面只是舉例,要印出同樣的結果,我們的to_string 要稍微改寫:
addr = self.val.address
size = self.val['d']['size']
i = 0 
content = []
while i < length:
  c = self.val['d']['data'][i]
  if c > 127:
    content.append("\\u%x" % (int(c)))
  else:
    content.append(chr(c))
  i = i + 1 
return '(QString)%s (length=%d): "%s"' % (addr, size, "".join(content))
當然我在查這個的時候,發現有另一個人的實作生猛得多:
http://nikosams.blogspot.tw/2009/10/gdb-qt-pretty-printers.html
dataAsCharPointer = self.val['d']['data'].cast(gdb.lookup_type("char").pointer())
content = dataAsCharPointer.string(encoding='utf-16', length=size*2)
它直接用cast 得到一個新的gdb.Value(),型態為char pointer;再呼叫string()方法用utf-16 的方式encoding,就把字串給拿出來了,更多用法可以參考文件,這裡暫時不詳細說明:
https://sourceware.org/gdb/onlinedocs/gdb/Values-From-Inferior.html

不過,這樣的pretty printer 其實有個問題的,因為它寫死了一定會去讀取self.val['d']裡面的值,但這個值未必是有初始化的,這時使用pretty printer 就會出錯:
<error reading variable: Cannot access memory at address 0xa>

其實要用pretty printer 的話,在.gdbinit 裡面加上 set print pretty on 就是個不錯的開始,光這樣就能讓一些輸出漂亮很多(其實也就是塞在一行裡跟自動換行的差別XDDD),相對開了這個print 也會佔掉比較多行數。
一般除非是客製的資料結構又有大量debug 的需求,才會需要客製化pretty printer,像是gdb 內應該已內建C++ std 的pretty printer,Qt 的開發套件應該也有提供Qt 的;寫rust時所用的rust-gdb也只是對gdb 進行擴展,在開始時先引入設定檔,裡面也有Rust 的pretty printer。
# Run GDB with the additional arguments that load the pretty printers
PYTHONPATH="$PYTHONPATH:$GDB_PYTHON_MODULE_DIRECTORY" gdb \
  -d "$GDB_PYTHON_MODULE_DIRECTORY" \
  -iex "add-auto-load-safe-path $GDB_PYTHON_MODULE_DIRECTORY" \
  "$@"

但一般人似乎不太用得到這個功能,所以這篇其實是廢文一篇(蓋章,感謝大家貢獻兩分鐘的時間給小弟的blog (炸。

2016年8月13日 星期六

新Top 介紹

top 一直是command line 上相當重要的工具,一打開就列出幾個高消耗的行程,等著kill (X,有時候不小心把電腦給炸掉的時候還滿好用的。
不過不知啥時更新後,新版的top 介面大改之後,我就不會用了OAO

最近決定把top 的用法給好好研究一遍,其實也就是把manpage 看過一遍啦,這裡做點筆記:

進到top 後,主要分成
1) Summary Area; 
2) Fields/Columns Header; 
3) Task Area

幾個沒變的命令:
h是help,q是quit沒變。
上下左右,page up/down, home, end,調位置

Summary Area 沒啥好說,顯示uptime, load average, Task 和CPU 的狀態
CPU 的狀態可用 t 來toggle顯示方式,可以關掉CPU 顯示,下面資料全部顯示,或者只顯示us+ni / sy total
用 1 來toggle 顯示全部的CPU 亦或合成一個

顯示的縮寫意思:
us, user : time running un-niced user processes
sy, system : time running kernel processes
ni, nice : time running niced user processes
id, idle : time spent in the kernel idle handler
wa, IO-wait : time waiting for I/O completion
hi : time spent servicing hardware interrupts
si : time spent servicing software interrupts
st : time stolen from this vm by the hypervisor

Memory 的狀態用 m 來選擇顯示方式 Used/Avail graph 或都純文字顯示

Field/Column 大概是跟舊版比起來變最多的,這裡可以用f 進到managing fields來設定要顯示的欄位,按f 後在想要的資訊用 space 或 d 來選擇要不要印出,用s 來設定用哪個欄位排序。

欄位基本上都有註解,我預設沒特別設定印出的欄位會是:
PID USER PR NI VIRT RES %CPU %MEM TIME+ S COMMAND
事實上可以印的東西很多,可以自行看manpage的介紹,不過我覺得實際上需要的其實也就預設這幾個。
其實寫這篇最主要的目的就是sort 了,因為開了top 都不知道要kill 誰了;現在自行設定用CPU 來sort,之後新的top 就像舊的top 一樣,把佔用最多CPU 的行程放在最上面,等著我們kill (X

另外也有些快捷鍵能設定排序欄位:
M: %MEM
N: PID
P: %CPU
T: TIME+

另外還有一些有趣的global command:
d: 設定更新的頻率
E/e: 設定Summary Area/Task Window記憶體的單位,從KiB 到EiB (真的有人有這麼多記憶體嗎XD)都行
g: 新的top 可以開四個不同的顯示視窗,可以有各自設定,用g 來選擇
k: 大殺四方行程,這跟原本的top 是一樣的
r: renice, 就…就是renice
L: 定位字串,如果要highlight 某個關鍵字可用,類似vim 裡面的 '/',找下一個則是 &

如果要改變畫面的顏色mapping,可以用Z 進到互動設定
用b/B toggle粗體顯示,z 設定是否彩色顯示,另外能設定各欄位的顏色:
S = Summary Data, M = Messages/Prompts,
H = Column Heads, T = Task Information
不過我是沒什麼美感的人,去改配色大概只會悲劇,所以就放著讓它用預設顏色就好。

初看新版的top ,應該是舊的top 功能不夠了,所以整個大翻修,還有許多功能本篇沒有介紹,剩下的大概都是平常用不太到的功能吧,就留給有興趣的人去研究了。

2016年7月5日 星期二

archlinux 上使用archlinux-java 切換不同java 版本

Java 2014 年就推出java 8 了,從java 6 到java 8 共有三個版本的 java,各版本間無法相容,例如要開發Android 的話就要使用java 6,而目前電腦上安裝的Eclipse Mars 2.0,看到java 6 就會作嘔回報:
Version 1.6.0_45 of the JVM is not suitable for this product. Version: 1.7 or greater is required.

在Ubuntu 上開發時,可以使用alternatives來切換不同的java 版本
http://lj4newbies.blogspot.tw/2007/04/2-jvm-on-one-linux-box.html

這個問題至少一年前在archlinux 還沒有解決,記得那時候為了修android 的課程裝了AUR的java 6,後來要寫minecraft plugin java 6 就被我刪了

幸好最近發現已經有解決方案了:archlinux-java
https://wiki.archlinux.org/index.php/java#Switching_between_JVM

使用上很直覺,透過status 檢視目前安裝哪些java 版本:
Available Java environments:
java-6-jdk
java-6-jre/jre
java-7-openjdk (default)

透過set 選擇要改用哪個版本:
archlinux-java set java-6-jre/jre

因為這些動作都會改動 /usr 的內容,所以都需要super user 權限。

當然wiki 上也有教你如何把java 打包成archlinux-java 接受的格式,不過一般人應該用不到這個:
https://wiki.archlinux.org/index.php/java#Package_pre-requisites_to_support_archlinux-java

2016年5月13日 星期五

在X86機器上debug ARM 執行檔

以後有可能會用到,寫這篇純粹做個記錄。

故事是這樣子的,最近閒來無事研究一下傳說中 jserv 大神的amacc,有些地方實在看不出程式執行至此時一些變數的值為何,這時我們就要用gdb 了
不過amacc 是用 arm-linux-gnueabihf-gcc 編出來的arm 執行檔,我們host gdb 是X86 在執行時就會報錯:可執行檔格式錯誤
如果用arm-linux-gnueabihf-gdb 呢:它會寫 Don't know how to run.

我們需要用到server-client 的架構,server 端在實體target 上可以用 gdbserver,這會需要對gdb-server, gdb 特別編譯;gdb configure target 為arm-linux-gnueabi,gdbserver configure host 為arm-linux-gnueabi。
如這篇所述:
https://sourceware.org/gdb/wiki/BuildingCrossGDBandGDBserver

我們可以用qemu 的 debug 來代替gdbserver:
首先執行qemu ,指定debug 的port 為9453:
qemu-arm -L /usr/arm-linux-gnueabihf -g 9453 amacc tests/shift.c

在另外一個終端機,打開arm-linux-gnueabihf-gdb,這是已經configure target為arm-linux-gnueabi 的gdb:
arm-linux-gnueabihf-gdb ./amacc tests/shift.c

在gdb 裡面連接remote target:
target remote localhost:9453

再來就能用c 開始跑了,happy debug。

相關參考:
http://kezeodsnx.pixnet.net/blog/post/31901130-gdbserver-remote-debug-%E6%B8%AC%E8%A9%A6

2015年4月26日 星期日

Linker script 簡介

Linker script,就是給Linker 看的script。

Linker:
當然這樣是在講廢話,首先要先知道Linker 是什麼:在程式編譯成物件檔之後,會把所有的物件檔集合起來交給連結器(linker),Linker 會把裡面的符號位址解析出來,定下真正的位址之後,連結成可執行檔。
例如我們在一個簡單的C 程式裡,include 一個標頭檔並使用裡面的函數,或者用extern 宣告一個外部的變數,在編譯成標頭檔的時候,編譯器並不清楚最終函數和變數的真正位址,只會留下一個符號參照。
待我們把這些東西送進linker,linker就會把所有的標頭檔整理起來,把程式碼的部分整理起來、變數的部分整理起來,然後知道位址了就把位址都定上去,如果有任何無法解析的符號,就會丟出undefined reference error。

我們可以試試:
外部函數,在一個foo.h 裡宣告,並在foo.c 裡面定義:
int foo();

外部變數,在var.c 裡面定義
int var;

在main.c 裡面引用它們:
#include “foo.h”
extern int var;
int main(){
  var = 10000;
  foo();
  return 0;
}

開始編譯
gcc -c main.c
gcc -c foo.c

這樣我們就得到兩個物件檔 main.o跟foo.o,我們可以用objdump -x 把物件檔main.o的內容倒出來看看,其中有趣的就是這個:
SYMBOL TABLE:
0000000000000000 g F .text 000000000000002a main
0000000000000000 *UND* 0000000000000000 var
0000000000000000 *UND* 0000000000000000 foo RELOCATION RECORDS FOR [.text]:
OFFSET TYPE VALUE
0000000000000011 R_X86_64_PC32 var-0x0000000000000008
000000000000001f R_X86_64_PC32 foo-0x0000000000000004

可以看到var, foo 這兩個符號還是未定(UND, undefined),若我們此時強行連結,就會得到:
main.c:(.text+0x11): undefined reference to 'var'
main.c:(.text+0x1f): undefined reference to'foo'

必須把foo.o 跟var.o 兩個檔案一起連結才行。

--
Linker script:
好了Linker講了這麼多,那linker script 呢?

Linker script 可以讓我們對linker 下達指示,把程式、變數放在我們想要的地方,一般的gcc 都有內建的linker script,平常我們開發x86系統跟arm系統,會使用不同的gcc,就是在這些預設的設定上有所不同,要是把這團亂七八糟的東西每key一次gcc 都要重輸入就太麻煩了;可以用ld --verbose 輸出,這裡看到的是支援x86 系統的linker script ,講下去又另一段故事,先跳過不提。

我們這裡拿燒錄在STM32 硬體上的linker script 來講,linker script 可見:
https://github.com/yodalee/mini-arm-os/blob/master/02-ContextSwitch-1/os.ld

Linker 的作用,就是把輸入物件檔的section整理成到輸出檔的section,最簡單的linker script 就是用SECTIONS指令去定義section 的分佈:
SECTIONS
{
. = 0x10000;
.text : { *(.text) }
. = 0x8000000;
.data : { *(.data) }
.bss : { *(.bss) }
}

在Linker script 裡面,最要緊的就是這個符號 '.' location counter,你可以想像這是一個探針,從最終執行檔的頭掃到尾,而 '.' 這個符號就指向現在掃到的位址,你可以讀取現在這個探針的位址,也可以移動探針。
不指定的話location counter 預設會從0的位置開始放置,而這段script,先把location counter 移到0x10000,在這裡寫入.text section,再來移到0x8000000放.data 跟.bss。
這裡檔名的match 支援適度的正規表示式,像*, ?, [a-z] 都可以使用,在這裡用wildcard直接對應到所有輸入檔案的sections。
光是SECTION 就講不清的用法,把指定某檔案的Section (file.o(.data)),排除某些檔案的section (EXCLUDE_FILE)
幸運的是,通常我們都不會想不開亂改linker script,這些位置的放法要看最終執行的硬體而定,亂放不會有什麼好下場。
另外linker script 也定義一些指令,這裡列一些比較常用的:

ENTRY:
另外我們可以用ENTRY指定程式進入點的符號,不設定的話linker會試圖用預設.text 的起始點,或者用位址0的地方;在x86 預設的linker script 倒是可以看到這個預設的程式進入點:
ENTRY(_start)

既然linker script 是用來解析所有符號的,那它裡面能不能有符號,當然可以,但有一點不同,一般在C 語言裡寫一個變數的話,它會在symbol table 裡面指明一個位址,指向一個記憶體空間,可以對該位址讀值或賦值;而在linker script 裡的符號,就只是將該符號加入symbol table內,指向一個位址,但位址裡沒有內容,定義這個符號就是要取位址。
一般在linker script 裡面定義符號,都是要對記憶體特定位址作操作:
以上面的STM32 硬體為例,因為FLASH 記憶體被map 到0x00000000,RAM的資料被指向0x20000000,為了把資料從FLASH 搬到RAM 裡,在linker script 的RAM 兩端,加上了:
_sidata = .;
//in FLASH _sdata = .;
_edata = .;

等於是把當前 location counter 這根探針指向的位址,放到_sdata 這個符號裡面,所以在主程式中,就能向這樣取用RAM 的位址:
extern uint32_t _sidata;
extern uint32_t _sdata;
extern uint32_t _edata;

uint32_t *idata_begin = &_sidata;
uint32_t *data_begin = &_sdata;
uint32_t *data_end = &_edata;
while (data_begin < data_end) *data_begin++ = *idata_begin++;

注意我們用reference 去取_sdata, _edata 的位址,這是正確用法。

Linker script 還定義了PROVIDE 指令,來避免linker script 的符號跟C中相衝突,上面如果在C程式裡有_sdata的變數,linker 會丟出雙重定義錯誤,但如果是
PROVIDE(_sdata = .)
就不會有這個問題。

KEEP 指令保留某個符號不要被最佳化掉,在script 裡面isr_vector是exception handler table,如果不指定的話它會被寫到其他區段,可是它必須放在0x0的地方,因此我們用KEEP 把它保留在0x0上。

MEMORY:
Linker 預設會取用全部的記憶體,我們可以用MEMORY指令指定記憶體大小,例子中我們指定了FLASH跟RAM的輸出位置與大小:
MEMORY { FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 128K
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 40K

} 接著我們在上面的SECTION部分,就能用 > 符號把資料寫到指定的位置
也就是例子裡,把 .text section全塞進 FLASH位址的寫法,如果整體程式碼大於指定的記憶體,linker 也會回報錯誤。

結語:
Linker 其實是個古老而複雜的東西,Linker script 裡面甚至有OVERLAY這個指令,來處理overlay 的執行檔連結,但一般來說,除非是要寫嵌入式系統,需要對執行檔的擺放位置做特別處理,否則大部分的程式都不會去改linker script,都直接用預設的組態檔下去跑就好了。

這篇只介紹了極限基本的linker script,完整內容還是請看文件。

參考內容:
Linker script document:
https://sourceware.org/binutils/docs/ld/Scripts.html

如果要知道linker如何處理位置無關符號,請見:
https://www.technovelty.org/c/position-independent-code-and-x86-64-libraries.html

2015年4月13日 星期一

用llvm 編譯嵌入式程式

最近幾天在研究嵌入式系統,玩一玩也有一些心得。
課程上所用的編譯工具是arm-none-linux-gnu toolchain,在Archlinux 下可以用如下的方式安裝:
$ yaourt -S gcc-linaro-arm-linux-gnueabihf
$ yaourt -S qemu-linaro
$ yaourt -S arm-none-eabi-gcc49-linaro
$ yaourt -S arm-none-eabi-gdb-linaro
$ ln -s /opt/gcc-linaro-arm-linux-gnueabihf/libc /usr/arm-linux-gnueabihf

不過最近心血來潮,想來試試如果用另一套編譯器 LLVM 來編譯看看,至於為什麼…好玩嘛(炸),總之這裡是設定筆記:

主要參考網址:
http://clang.llvm.org/docs/CrossCompilation.html
https://github.com/dwelch67/mbed_samples/

用上LLVM 的優勢是,它在編譯時會將程式碼轉換成與平台無關的中間表示碼(Intermediate Reprsentation, IR),再透過轉換器轉成平台相關的組合語言或是底層的機械器。不像gcc 針對不同的Host/Target的組合就是不同的執行檔和標頭檔,在編譯到不同平台時,都要先取得針對該平台的gcc 版本。
註:上面這段是譯自上面的參考網址,雖然我有點懷疑這段話,不然gcc 命令列參數那些平台相關的選項是放好看的嗎?

我嘗試的對象是mini-arm-os 00-helloworld,目標device 是STM32
https://github.com/embedded2015/mini-arm-os

前端的 c 我們先用clang 編譯為llvm IR code,用llvm 的llc 編譯為 object file,因為目前LLVM 的linker lld還在開發中,只能link x86上的elf 檔,要連結ARM 我們在link 階段還是只能用biutils 的ld,以及biutils 的objcopy,這樣看起來有點詭異,有點像換褲子結果褲子只脫一半就穿新褲子的感覺。

最後的Makefile 大概長這樣:

CC := clang
ASM := llc
LINKER := arm-none-eabi-ld
OBJCOPY := arm-none-eabi-objcopy

CCFLAGS = -Wall -target armv7m-arm-none-eabi
LLCFLAGS = -filetype=obj
LINKERSCRIPT = hello.ld

TARGET = hello.bin
all: $(TARGET)

$(TARGET): hello.c startup.c
$(CC) $(CCFLAGS) -c hello.c -o hello.bc
$(CC) $(CCFLAGS) -c startup.c -o startup.bc
$(ASM) $(LLCFLAGS) hello.bc -o hello.o
$(ASM) $(LLCFLAGS) startup.bc -o startup.o

$(LINKER) -T hello.ld startup.o hello.o -o hello.elf
$(OBJCOPY) -Obinary hello.elf hello.bin

其實重點只有在target 指定的地方,其他的就沒啥,只是這樣一看好像沒有比較方便,而且這樣根本就不是用 llvm 編譯,最重要的Link 階段還不是被 gcc 做去了= =
在lld 完成前也許還是乖乖用 gcc 吧?

對LLVM 相關介紹可以見:
http://elinux.org/images/d/d2/Elc2011_lopes.pdf
各種biutils 的替代品列表:
http://marshall.calepin.co/binutils-replacements-for-llvm.html
LLVM lld開發中,是不是該進去貢獻一下Orz:
http://lld.llvm.org/