# [高階Unix]期中考筆記
###### tags: `碩一上修課` `高階Unix`
[note](https://wiki.kshuang.xyz/doku.php/course:nctu-%E9%AB%98%E7%AD%89unix%E7%A8%8B%E5%BC%8F%E8%A8%AD%E8%A8%88)
## 考試說明
地點 : 資電館326電腦教室
時間 : 2019/11/8
工作環境 : Ubuntu 18.04 有manual pages可以查,
workstation也有提供Slides
不能用網路
有6-7題,請熟讀以下關鍵字
gdb, LD_PRELOAD, GOT, Homework #1, and Homework #2.
## gdb (01-ov+tools p54~72)
* [Reference Material](https://b8807053.pixnet.net/blog/post/336154079-%5B%E8%BD%89%E8%B2%BC%5Dgdb-%E4%BB%8B%E7%B4%B9)
* A command line based(interactive) debugger
* All source codes must be compiled with the -g option
* then run the program excutable file:
```
gcc -g test.c
gdb a.out
```
* Frame and Stack
* Frame
gdb將程式碼以**副程式**為單位分成一個個**Frame**,例如說main()是一個frame,function a()被視為另一個frame。因此利用gdb debug時,可局部針對某個Frame去執行,跳至上一個或下一個frame,執行至該frame結束等等
* Stack
| Frame2 | <-- 上上一層...以此類推 |
| ------ | ---------------------|
| Frame1 | <-- 呼叫Frame0的 |
| Frame0 | <-- 正在執行的 |
在進入另一個Frame之前,會將該frame的一些變數存在Stack中
* Basic commands
* list [line # | function | file: line # | file function]: Show source code
* run( r): 執行程式,或是從頭再執行程式
* help(h): gdb 指令的說明, ex. help list
* file:
* kill: 終止程式的執行
* backtrace(bt): 堆疊追蹤,顯示上層所有frame的簡略資訊
* print(p): 印出變數內容, ex. print a
* whatis: 印出變數的型態, ex. whatis a
* breakpoint(b, bre, break) [line # | function | file: line # | file function]: 設定中斷點
* 用 info breakpoint(info b)來查看設定了哪些中斷點
* 當程式被中斷時也可以透過 info line 來查看現在停在哪一行
* 用clear去清掉已設好的中斷點
* continue: 繼續執行,和breakpoint搭配使用
* next (n):單步執行,但遇到 frame 時不會進入 frame 中單步執行。
* step (s):單步執行。但遇到 frame 時則會進入 frame 中單步執行。
## GOT (07-procenv P17~23)
https://blog.csdn.net/qq_18661257/article/details/54694748
## LD_PRELOAD (07-procenv P24~31)
https://ixyzero.com/blog/archives/3137.html
https://medium.com/fcamels-notes/linux-%E7%B7%A8%E8%AD%AF-shared-library-%E7%9A%84%E6%96%B9%E6%B3%95%E5%92%8C%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A0%85-cb35844ef331
https://medium.com/fcamels-notes/linux-%E5%9F%B7%E8%A1%8C%E6%99%82%E5%B0%8B%E6%89%BE-symbol-%E7%9A%84%E6%B5%81%E7%A8%8B%E4%BB%A5%E5%8F%8A-shared-library-%E7%9B%B8%E9%97%9C%E7%9F%A5%E8%AD%98-b0cf1e19cbf3
## File permission
* Real user ID and real group ID
* 真正的身分證
* 由登入時的password file所使用
* Effective user ID and effective group ID
* used for file access permission
* **例子**
若root是一個webservice的擁有者,但權限是要透過EUID、EGID去檢查。原則上UID和EUID會是一樣的,但為了讓webservice拿不到root的權限,所以可能就會把EUID改成一般使用者的權限
* set by setuid() and setgid()
* Saved set-user-ID and saved set-group-ID
* 另外一種特別的權限,只有root能去set
暫時用管理者權限去取代原有caller的權限
* File permission bit
* 通常用八進位表示, e.g. 0755 <==> 111 101 101
* owner(r-w-x), group(r-w-x), other(r-w-x)
## so檔
so其實就是shared object的意思。今天看了上面的部落格,感覺好吃力。趕緊做個筆記記錄一下。下面的內容大多都是連線中的,穿插我自己的筆記
牽扯到ELF格式,gcc編譯選項待補,簡單實用的說明一下,對Linux下的so檔案有個實際性的認識。
1.so檔案是什麼?
2.怎麼生成以及使用一個so動態庫檔案?
3.地址空間,以及執行緒安全.
4.庫的初始化,解析:
5.使用我們自己庫裡的函式替換系統函式:
1.so檔案是什麼?
也是ELF格式檔案,共享庫(動態庫),類似於DLL。節約資源,加快速度,程式碼升級簡化。
知道這麼多就夠了,實用主義。等有了印象再研究原理。
2.怎麼生成以及使用一個so動態庫檔案?
先寫一個C檔案:s.c
```c=
#include <stdio.h>
int count;
void out_msg(const char *m)
{//2秒鐘輸出1次資訊,並計數
for(;;) {printf("%s %d\n", m, ++count); sleep(2);}
}
```
編譯:得到輸出檔案libs.o
gcc -fPIC -g -c s.c -o libs.o
----------------------------------------------------------------------
-fPIC: -fPIC作用於編譯階段,告訴編譯器產生與位置無關程式碼(Position-Independent Code),則產生的程式碼中,沒有絕對地址,全部使用相對地址,故而程式碼可以被載入器載入到記憶體的任意 位置,都可以正確的執行。這正是共享庫所要求的,共享庫被載入時,在記憶體的位置不是固定的。
-g: 令 gcc 生成除錯資訊,該選項可以利用作業系統的“原生格式(native format)”生成除錯資訊。GDB 可以直接利用這個資訊,其它偵錯程式也可以使用這個除錯資訊
-c: 僅執行編譯操作,不進行連線操作。
-o: 指定生成的輸出檔名稱
注意!-c,-o不是指.c檔案和.o檔案!!
----------------------------------------------------------------------
連結:得到輸出檔案libs.so
gcc -g -shared -Wl,-soname,libs.so -o libs.so libs.o -lc
-----------------------------------------------------------------------
上述語句中 libs.o是輸入檔案
-shared:
Produce a shared object which can then be linked with other objects to form an executable. Not all systems support this option. For predictable results, you must also specify the same set of options used for compilation (-fpic, -fPIC, or model suboptions) when you specify this linker option.
-Wl: 注意第二個字母是小寫的L,不是I
Pass option as an option to the linker. If option contains commas, it is split into multiple options at the commas. You can use this syntax to pass an argument to the option. For example, -Wl,-Map,output.map passes -Map output.map to the linker. When using the GNU linker, you can also get the same effect with -Wl,-Map=output.map.
-soname:
soname的關鍵功能是它提供了相容性的標準:
當要升級系統中的一個庫時,並且新庫的soname和老庫的soname一樣,用舊庫連結生成的程式使用新庫依然能正常執行。這個特性使得在Linux下,升級使得共享庫的程式和定位錯誤變得十分容易。
在Linux中,應用程式通過使用soname,來指定所希望庫的版本,庫作者可以通過保留或改變soname來宣告,哪些版本是相容的,這使得程式設計師擺脫了共享庫版本衝突問題的困擾。
-lc:
-l 是直接加上某庫的名稱,如-lc是libc庫 -L 是庫的路徑,搜尋的時候優先在-L目錄下搜尋
一個頭檔案:s.h
```c=
#ifndef _MY_SO_HEADER_
#define _MY_SO_HEADER_
void out_msg(const char *m);
#endif
```
再來一個C檔案來引用這個庫中的函式:ts.c
```c=
#include <stdio.h>
#include "s.h"
int main(int argc, char** argv)
{
printf("TS Main\n");
out_msg("TS ");
sleep(5); //這句話可以註釋掉,在第4節的時候開啟就可以。
printf("TS Quit\n");
}
```
編譯連結這個檔案:得到輸出檔案ts
gcc -g ts.c -o ts -L. -ls
執行./ts,嗯:成功了。。。還差點
得到了ts:error while loading shared libraries: libs.so: cannot open shared object file: No such file or directory
系統不能找到我們自己定義的libs.so,那麼告訴他,修改變數LD_LIBRARY_PATH,為了方便,寫個指令碼:e(檔名就叫e,懶得弄長了)
```
export LD_LIBRARY_PATH=${pwd}:${LD_LIBRARY_PATH}
./ts
```
執行:./e &
螢幕上就開始不停有資訊輸出了,當然TS Quit你是看不到的,前面是個死迴圈,後面會用到這句
----------------------
& 放在啟動引數後面表示設定此程序為後臺程序。預設情況下,程序是前臺程序,這時就把Shell給佔據了,我們無法進行其他操作,對於那些沒有互動的程序,很多時候,我們希望將其在後臺啟動,可以在啟動引數的時候加一個'&'實現這個目的。
----------------------
3.地址空間,以及執行緒安全:
如果這樣:
./e &開始執行後,稍微等待一下然後再 ./e&,
這個時候螢幕資訊會怎麼樣呢?全域性變數count會怎麼變化?
會是兩個程序交叉輸出資訊,並且各自的count互不干擾,雖然他們引用了同一個so檔案。
也就是說只有程式碼是否執行緒安全一說,沒有程式碼是否是程序安全這一說法。
4.庫的初始化,解析:
windows下的動態庫載入,解除安裝都會有初始化函式以及解除安裝函式來完成庫的初始化以及資源回收,linux當然也可以實現。
ELF檔案本身執行時就會執行一個_init()函式以及_fini()函式來完成這個,我們只要把自己的函式能讓系統在這個時候執行
就可以了。
修改我們前面的s.c檔案:
```c=
#include <stdio.h>
void my_init(void) __attribute__((constructor)); //告訴gcc把這個函式扔到init section
void my_fini(void) __attribute__((destructor)); //告訴gcc把這個函式扔到fini section
void out_msg(const char *m)
{
printf(" Ok!\n");
}
int i; //仍然是個計數器
void my_init(void)
{
printf("Init ... ... %d\n", ++i);
}
void my_fini(void)
{
printf("Fini ... ... %d\n", ++i);
}
```
重新制作 libs.so,ts本是不用重新編譯了,程式碼維護升級方便很多。
然後執行: ./e &
可以看到螢幕輸出:(不完整資訊,只是順序一樣)
Init
Main
OK
Quit
Fini
可以看到我們自己定義的初始化函式以及解析函式都被執行了,而且是在最前面以及最後面。
如果s.c中的sleep(5)沒有註釋掉,那麼有機會:
./e&
./e&連續執行兩次,那麼初始化函式和解析函式也會執行兩次,雖然系統只加載了一次libs.so。
如果sleep時候kill 掉後臺程序,那麼解析函式不會被執行。
5.使用我們自己庫裡的函式替換系統函式:
建立一個新的檔案b.c:我們要替換系統函式malloc以及free(可以自己寫個記憶體洩露檢測工具了)
```c=
#include <stdio.h>
void* malloc(int size)
{
printf("My malloc\n");
return NULL;
}
void free(void* ad)
{
printf("My free\n");
}
```
老規矩,編譯連結成一個so檔案:得到libb.so
gcc -fPIC -g -c b.c -o libb.o
gcc -g -shared -Wl,-soname,libb.so -o libb.so -lc
修改s.c:重新生成libs.so
```c=
void out_msg()
{
int *p;
p = (int*)malloc(100);
free(p);
printf("Stop Ok!\n");
}
```
修改指令碼檔案e:
```c=
export LD_PRELOAD=${pwd}libb.so:${LD_PRELOAD}
export LD_LIBRARY_PATH=${pwd}:${LD_LIBRARY_PATH}
./ts
```
關鍵就在LD_PRELOAD上了,這個路徑指定的so將在所有的so之前載入,並且符號會覆蓋後面載入的so檔案中的符號。如果可執行檔案的許可權不合適(SID),這個變數會被忽略。
執行:./e &
嗯,可以看到我們的malloc,free工作了。
## Homework #1
[作業1題目中文整理](/Q-8m4ZqPSayX96y8i5keBQ?both)
## Homework #2
[作業2題目中文整理](/oMe-BNv5Rt6MP6qYFLk6Lg?both)