伊莉討論區

標題: 有關全域變數的問題 [打印本頁]

作者: caoh    時間: 2017-7-27 04:22 PM     標題: 有關全域變數的問題

請問一下,我最近看書的時候看到 dll / so 的重定位的部分
參考這裡:   
    https://www.technovelty.org/c/po ... 6-64-libraries.html

最下面有一張圖,他對於全域變數會使用一個 GOT 的表格來做重定位
不管它是模組內,還是模組外的變數,都是這樣。
然後它說這樣假如有兩個 int global 那麼可以由 loader 決定要用哪一個

我在其他地方也有說如果有一個 main 去使用 int global
那麼 main.o 會有自己的副本,而 so 模組內部對 int global 的引用會去引用 main 的

那我的問題是,如果有兩個 .so 都有 int global 不會有問題嗎?
而 main 這樣的情況也不會違反 ODR 嗎?




補充內容 (2017-7-28 04:44 PM):
像底下這個例子 main 對 testfn 的呼叫竟然跟 link 順序有關

https://stackoverflow.com/questi ... d-by-g-in-this-case
作者: J25839674    時間: 2017-7-29 10:37 PM

樓主你好,小弟是一位學生,才識淺薄,如果錯誤請多指教<(_ _)>,希望你下可以幫助到你

我把問題理解成:如果已經引入一個全域變數/定義function ,後來經過重定義,但重定義用的兩份資料有相同名稱的全域變數/定義function,為甚麼不會發生錯誤?

通常(不通常的我不知道對不起)在做連結object時,會從第一個連結庫開始定義,例如:連結順序為 new1 new2 然而我要定義的變數為object1 他會先從new1尋找object1 ,如果找到object1會被歸類為已被定義,之後從new2找到的object1時,他會被忽略,另外在做連結時,如果連結庫C完全用不到的話,他會被丟棄,然而之後要用到連結庫C的定義時,會發生未知定義的錯誤,所以連結庫B需要連結庫A時,應要把連結庫B放在連結庫A之前。

希望有幫到你

#一個想磨練自己的學生
作者: caoh    時間: 2017-7-30 12:12 AM

你好

其實我比較熟悉的是 C++ 不熟悉 C,所以我一直腦中有個 One Definition Rule 的教條
所以在我認知中,若兩個連結的模組,是不能有相同的變數定義,這會造成重複定義的錯誤

我的補充內容網址有人爭論道:The code is breaking the rule, not the compiler
不過這個網址的例子是 main 去連結兩個 .so 中的共同名稱變數結果會跟連結順序有關
小弟另外好奇的是如果其中一個 .so 引用另一個 .so 但兩個同時有相同名稱變數會怎樣

我不清楚會發生忽略這樣的事情,所以是 C 會這樣嗎?
因為我想這樣好像怪怪的,變成你使用某個變數,你以為
在用 A.so 這一個但其實是在用 B.so 這一個,好像滿恐怖的
  
  



作者: J25839674    時間: 2017-7-30 04:44 PM

本帖最後由 J25839674 於 2017-7-30 05:00 PM 編輯

樓主早安

看到第二段,瞬間以為我錯了.........XD

他與是用C或C++無關 ,這是組語的問題

我又再試一次,他是可以編譯並執行的,我們先看看以下code

https://ideone.com/fork/oGxUKT

如果我有兩個連結庫A和連結庫B,首先在main裡有三個函式,function在A和B都有,function_error在A和B都沒有。(printf在cstdio)

連結順序 A and B

在compiler時,他會將main的object分為兩類,一類為未定義,一類為已定義

未定義:
function;
function_error;

已定義:
printf;(在cstdio引入後)

她開始從A尋找function;
如果找到 function,他會被歸類在已定義,然後會繼續在A尋找function_error,如果找到function_error,他也會被歸類在已定義,當main沒有未定義的object時,他會取消對下一個連結庫也就是B的搜尋,並捨棄B

但當function_error在A搜尋不到時,他會繼續搜尋B,但B也沒有,所以會CE~~~~XD,他也會捨棄B,因為用不到

接下來看看這兩份code 分別為A和B

https://ideone.com/fork/OXTiB1   for A
https://ideone.com/fork/OzStcR   for B

連結順序一樣  A and B

現在把 main 裡面的 function_error這一行刪除,在編譯時搜尋完A時,在main中,已經沒有未定義的object了,但他會發現function使用了so2,這裡要注意,如果function沒有使用so2那接下來的步驟將不會執行,當他發現so2未定義時,他會繼續搜尋B,當他搜索到B的function他會去比對so2的變數名稱,function與so2是不同,所以他會繼續搜尋下去,不會對function做任何處理,因為這一步是要定義so2,而非function,當他定義完so2時,他會結束搜尋,也就是說它會對B的function視若無睹。
我的疑惑:

可能是我離解錯問題了,這個好像無關C\C++,這比較像是動態連結的問題,比較偏低階處理的問題

希望可以望您的忙

#一個想磨練自己的學生

作者: J25839674    時間: 2017-7-30 04:46 PM

敏感內容好煩躁喔~~~~~~

對不起,處理敏感問題處理太舊了,結果最後還是得刪括弧QQ
作者: caoh    時間: 2017-7-30 06:35 PM

這就是奇怪的地方,在我貼那篇 Stackoverflow 的文章中,第一個回覆者有說
如果不作成程式庫,而直接把 main, test, test1 三個 cpp 連結在一起,就會
產生錯誤,應該是它不能違反 ODR 這個原則。但是當 test/test 作成程式庫
然後被 main 連結使用,就會出現你說的結果,不會發生錯誤,但也就跟連結順序有關了。

應該說這個現象是確定的,書上,網路上,你這邊測試都是這個結果
不過好奇他為什麼要這樣設計,這樣不產生錯誤而讓連結時期決定使用版本
有什麼好處?

同樣的情況在 windows 它就沒這樣作,它的 dll 需要帶一個 lib (import library)
如果連結兩個 lib 時名稱有重複,它會連結錯誤。
  
  


作者: J25839674    時間: 2017-7-30 09:36 PM

樓主晚安

當main test test1 一起編譯時,其實他會把它當作同一個檔案也就是會把三個檔案匯集成一個檔案再送去編譯,所以才會發生重複定義的問題。樓主理解的沒有錯。

然而,在做連結庫編譯後,連結庫已經算是半個程式了,已經轉換成組語了。所以在編譯過程就只是塞空格,滿了就好了。

至於好處,例如x86和x64的問題,在x64的系統上,有些開發者希望可以,盡量以x64來運行程式,在x64沒有的情況下,才運行x86

至於在windows系統上,我沒見過(?),是第三方函式庫嗎?還是調用x86和x64調用錯誤?,有實例可以看嗎?學習一下

#一個想磨練自己的學生
作者: caoh    時間: 2017-7-30 10:55 PM

晚安

作成庫之後,它會有一些輸出的符號,這樣別人才能夠引用
否則 main 要使用 function,連結器怎麼會知道 A/B 有 function?

這邊有一篇可以參考:
https://stackoverflow.com/questions/19373061/what-happens-to-global-and-static-variables-in-a-shared-library-when-it-is-dynam

基本上,main 在編譯的時候,會把 function 的呼叫做個記號
因為它這時候還沒辦法知道 function 的位置,所以只能給假的
但它會在一個重定位表之中記錄這行組語參考的位置必須被修正
等到載入器載入 A.so 的時候,才有辦法去修正那個位置,這個
在第一篇的第一個連結有詳細細節,這裡還有一篇可以參考。

http://eli.thegreenplace.net/2011/08/25/load-time-relocation-of-shared-libraries/

因為 windows 的作法是,main 會編出一個呼叫 function 的指令
然後連結的時候,會在 A.lib 之中找到這個 function 就可以連結了
在這個例子中,如果把 A.lib 跟 B.lib 同時提供,它就會發現這個
function 是重複定義的,它不知道要用誰的,不會依連結順序去做選擇
(在 windows 裡面作動態連結都會有這個輸入函式庫 import library,它是去連結這個 .lib 來確認是否有 function) 我印象中是不會有忽略的事。









作者: J25839674    時間: 2017-7-31 03:32 PM

樓主今天放颱風假真好~~~~~~QQ

我剛剛用自己的連結庫在windows上做測試,以結果來說,我感覺很神奇,雖然沒有發生錯誤,但是卻跟Linux相反。真是一個寶貴的經驗。(感覺好像又證明了什麼)

據上文,第五段以前,樓主提的,我都知道,但不明白為什麼要提出來?不過我對比較低階處理的事物,我沒有百分之百的把握,如果樓主是比較想知道低階處理的問題,我可能無力回答,十分抱歉。

看到第六段,我就去試試看了,但我並非使用windows的內建函式庫,也並非使用樓主所使用的連結器,可能會有差距,我是在想,在例子中的連結庫真的是動態連結嗎?因為lib的附檔名也使在靜態連結上,或許lib是一個動態連結函式庫,有可能連結器把它當作靜態連的函式庫,這樣就有可能造成錯誤。

這應該是我最後一次回帖了,因為要開學了.......QQ,感謝您的問題,讓我可以更深刻的檢視自己,希望這幾天的討論有幫助到您。

#一個想磨練自己的學生
作者: caoh    時間: 2017-7-31 04:27 PM

恩,謝謝你的回覆。


作者: a333221    時間: 2017-7-31 10:16 PM

本帖最後由 a333221 於 2017-7-31 10:17 PM 編輯

caoh 大想連結的函數庫是其他人提供的?還是都自己寫的?

修改 build 執行檔和函數庫時所下的參數,有沒有可能可以改變這些行為成想要的?
作者: caoh    時間: 2017-7-31 11:55 PM

本帖最後由 caoh 於 2017-8-1 07:14 AM 編輯

你好,我只是想知道 linux 中
為何把 global 引用作成需要 load time relocation 因為我覺得引用一個變數
如果是在模組內的,應該只需要連結時重定位,模組外的才需要載入時重定位

像 windows 如果沒有特別指定,它對於 extern 函數應該只是一個直接呼叫
即便實際是引用 dll 的函數它還是直接呼叫,若指定 __declspec(dllimport)
才編成間接呼叫,然後有個 .lib 來輔助連結。如果連結兩個 dll 有相同函數名
會產生連結錯誤。

不過 linux 不會,它作法好像不同,之前J大的測試碼說明先連結的被先選中
後面的會被忽略,不會有錯誤產生。另外就是如果我很確定它是一個模組內
extern 函數呼叫,不是外部共享函是庫中的函數,那如果它編成直接呼叫,
不是比間接呼叫有效率嗎?
--------------- 編輯 -----------

不好意思,我好像搞混了,它是編成一個需要載入時重定位的呼叫,但還是直接呼叫
參考這篇:Extra credit: Why was the call relocation needed?
只是需要載入時重定,由 loader 去設定模組內或外的定義,花費一些載入時間而已。





作者: a333221    時間: 2017-8-1 09:52 PM

caoh 發表於 2017-7-31 11:55 PM
你好,我只是想知道 linux 中
為何把 global 引用作成需要 load time relocation 因為我覺得引用一個變數
...

你好,全域變數的問題,使用 -Wl,-Bsymbolic 不知道能不能滿足你的需求

main.c
  1. int global = 12;

  2. void So();
  3. int main()
  4. {
  5.         So();
  6.         return 0;
  7. }
複製代碼
so.c
  1. #include <stdio.h>
  2. int global = 45;

  3. void So()
  4. {
  5.         print f("global = %d\n", global);
  6. }
複製代碼
建置參數
  1. gcc -shared -fPIC so.c -o libso.so
  2. gcc -shared -fPIC -Wl,-Bsymbolic so.c -o libso2.so
  3. gcc main.c ./libso.so -o main1
  4. gcc main.c ./libso2.so -o main2
複製代碼
main1 和 main2 的執行結果分別為:
main1: global = 12
main2: global = 45

至於兩個 so 檔擁有相同函數名稱的問題,目前沒有想法
作者: caoh    時間: 2017-8-1 10:08 PM

原來如此,這樣可以強制使用本地端的變數,謝謝你的回覆。
  
  

作者: o_g349    時間: 2017-9-12 08:56 PM

提示: 作者被禁止或刪除 內容自動屏蔽
作者: caoh    時間: 2017-9-13 08:05 PM

謝謝你的回應。

我後來有弄比較清楚一點了,linux 對於 .so 會當成是模組外的,這是一個選擇。
但它要支援 interpose 的功能,加上如果指定 -fPIC 的話,那麼即使呼叫一個 .c
內的全域函數,它仍然要編成 GOT 的間接呼叫,否則會因為 interpose 變成要
重定位,那 PIC 就不 PIC 了。但如果沒指定 -fPIC 或不是 .so 我就不是很清楚了。

windows 您這邊有點小誤,函數它預設是選模組內部的,可能您這裡是筆誤。
原因您都有寫到了,它會去直接呼叫 xxx 然後在經由它跳去 __imp_xxx 中。
內部的東西才有可能直接呼叫。

作者: o_g349    時間: 2017-9-14 02:03 AM

提示: 作者被禁止或刪除 內容自動屏蔽




歡迎光臨 伊莉討論區 (http://s03.p01.eyny.com/) Powered by Discuz!