2020年4月28日 星期二

OpenSSL 1.0.2 upgrade到 OpenSSL 1.1.1 遇到的memory leak



原本這blog幾乎就只是拿來當我side project的release note, 沒什麼拿來寫文章(文筆不好又沒什麼內容是想持續寫的, 寫文章又超花時間...), 不過最近想開始紀錄一些生命中遇到的Bug, 特別是那種網路上超難找到解方的bug, 希望寫的這些文章能剛好幫上遇到一樣問題的人~。

這次的內容是OpenSSL的memory leak, 在某專案上的service剛好發現有memory leak出現, 然後出問題的那段code是Dll plugin, 所以沒辦法直接用VLD (Visual Leak Detector)找問題點, 只能退而求其次選擇直接印heap用量在log上, 之後就用註解大法 + binary search + fiddler大量打request reproduce問題, 最後找出問題點是curl每打一次request就都會有memory leak發生, 而且這問題是發生在OpenSSL從1.0.2升級到1.1.1後, 也只有https的request才會有leak, http request不會有memory leak問題。

試到這邊也只能知道, 一樣的code在OpenSSL 1.1.1會有leak的問題, 而且OpenSSL 1.0.2只maintain到去年底, 所以還是得找出為什麼會Memory Leak, 就寫了個小測試程式, 掛上VLD測試, 以下是測試結果:

  1. 直接在main裡面用libcurl測試打http & https request, 都沒有memory leak發生
  2. 建thread並在裡面用libcurl測試打http & https request, 只有https會發生memory leak (就算只建一個thread測試一樣會發生, 所以不是thread safe問題, 看起來問題點是只要不是在main thread的話curl就會沒辦法正常釋放openssl的記憶體)
  3. 之後有嘗試不要用MT (static lib) build openssl改用MD (dll) build openssl, 就不會有memory leak問題

這樣測下來反而更玄了, 到底什麼bug才會符合上述所有情況, 後來我就猜該不會是我OpenSSL build壞了, 還是libCurl build壞導致它用完OpenSSL的resource沒有釋放掉。

後來乾脆直接去Curl的Github開issue, 直接抱Curl工程師的大腿了。
https://github.com/curl/curl/issues/5282

沒想到Curl工程師沒多久就立刻回覆了, 主要是說他們CI都有做memory leak的檢測, 像這種換linker導致leak的問題很奇怪, 因為這表示執行的是一模一樣的curl行為, 應該9成9不是curl的鍋。

好啦, 既然這樣只能繼續挖, 不過這樣感覺問題應該還是出在OpenSSL身上, 所以就繼續朝自己build壞OpenSSL的方向追, 不過試了不少方法還是沒結果, 最後終於找到有類似的問題在stackoverflow上(這issue明明就價值千金, 怎麼這麼冷門...)
https://stackoverflow.com/questions/60088679/c-openssl-1-1-1-running-rsa-algorithm-in-thread-causing-memory-leaks/60090384#60090384

解決方法就是, thread結束以前call一個API: OPENSSL_thread_stop()

測試了一下還真的解決, 超級傻眼, 這個API OpenSSL 1.0.2根本沒有, 是1.1.0之後才加的, 所以用之前的code跑才會有問題, 在去看官方的文件:
https://www.openssl.org/docs/man1.1.0/man3/OPENSSL_thread_stop.html

--------------------------

The OPENSSL_thread_stop() function deallocates resources associated with the current thread. Typically this function will be called automatically by the library when the thread exits. This should only be called directly if resources should be freed at an earlier time, or under the circumstances described in the NOTES section below.
The OPENSSL_INIT_LOAD_CONFIG flag will load a default configuration file. For optional configuration file settings, an OPENSSL_INIT_SETTINGS must be created and used. The routines OPENSSL_init_new() and OPENSSL_INIT_set_config_appname() can be used to allocate the object and set the application name, and then the object can be released with OPENSSL_INIT_free() when done.
--------------------------

--------------------------

NOTES

Resources local to a thread are deallocated automatically when the thread exits (e.g. in a pthreads environment, when pthread_exit() is called). On Windows platforms this is done in response to a DLL_THREAD_DETACH message being sent to the libcrypto32.dll entry point. Some windows functions may cause threads to exit without sending this message (for example ExitProcess()). If the application uses such functions, then the application must free up OpenSSL resources directly via a call to OPENSSL_thread_stop() on each thread. Similarly this message will also not be sent if OpenSSL is linked statically, and therefore applications using static linking should also call OPENSSL_thread_stop() on each thread. Additionally if OpenSSL is loaded dynamically via LoadLibrary() and the threads are not destroyed until after FreeLibrary() is called then each thread should call OPENSSL_thread_stop() prior to the FreeLibrary() call.
On Linux/Unix where OpenSSL has been loaded via dlopen() and the application is multi-threaded and if dlclose() is subsequently called prior to the threads being destroyed then OpenSSL will not be able to deallocate resources associated with those threads. The application should either call OPENSSL_thread_stop() on each thread prior to the dlclose() call, or alternatively the original dlopen() call should use the RTLD_NODELETE flag (where available on the platform).
--------------------------
簡單的說就是, 從OpenSSL 1.1.0開始, OpenSSL會開始自動分配thread資源, 可是根據某些情況你必須自己釋放, 以windows來說, 如果你的OpenSSL是build成MD DLL, 那除了某些特定情況需要自己手動釋放之外, OpenSSL會在thread結束時自動釋放記憶體; 而如果你的OpenSSL是build成MT static lib, 那你就必須自己在thread結束時自己call OPENSSL_thread_stop()主動釋放記憶體。 而Unix / Linux的情況則是相反, 大多情況OpenSSL都會在thread結束時自動釋放記憶體, 除非你動態載入openssl, 然後又提早unload它, 那你就必須自己主動釋放記憶體。

看完整個傻眼, 怎麼會有不同linker方式得寫不同的code處理, 這也太雷了吧..., , 找這bug花的時間搞不好比我直接去翻OpenSSL 1.1.0整本手冊還要長.....。 而且跟我實驗的結果還完全符合, 只是我結論的方向完全錯誤囧

這次分享的case就到這邊了, 希望我以後不要再遇到這麼噁心的bug / case, 或是遇到了剛好老天保佑能讓我快點找到解決方法...。

最後來個小推坑, 最近看完街角魔族了, 超級讚!!!  看完直接立刻重刷第二輪, 歡迎大家入坑XDD





2020年4月27日 星期一

MahoMangaDownloaderVer10.4更新

今天有使用者回報wnacg某個特定資源沒辦法下載, 測試了一下發現是壓縮檔壞檔, 因為下載器預設邏輯是有壓縮檔就下載壓縮檔, 沒壓縮檔就一頁一頁下載, 至於壞檔的邏輯---->

"是的, 下載器有處理壞檔的邏輯, 如果偵測到壓縮檔小於10 byte, 下載器就會視壓縮檔為壞檔, 改成一頁一頁下載的模式。"

至於為什麼這個沒偵測到, 因為這個壞檔的大小是15 byte .......

我應該真的沒被針對吧QQ  總之把壞檔偵測的大小調高, 就是這次的更新了...勤勉阿勤勉~~~。





Ver10.4 更新內容:
  • 加大wnacg網站zip壞檔偵測的大小解決壞檔誤判的問題


下載器Demo圖:





介紹:
https://project.zmcx16.moe/?page=mahomangadownloader


環境需求



簡單除錯:
  1. 如果下載失敗, 麻煩先用瀏覽器測試看資源是否存活。
  2. 如果能正常用瀏覽器瀏覽, 麻煩先查看LogFiles資料夾內的log檔案看錯誤訊息為何。
  3. 回報問題時, 麻煩提供有問題的網址以及log內容, 這樣我才有辦法測試找問題原因。



檔案位址:
https://drive.google.com/open?id=1iUtBAFcmFZrDUPsr1GeVBUFS1RM7xMmH

32位元版本:
https://drive.google.com/open?id=1_KqY9JG4_a4l4Yvj6yDCkmyGH7DysciI

解壓密碼:zmcx16



免責聲明:
******************
MahoMangaDownloader僅作為學術研究使用,禁止利用本程式行非法用途。

2020年4月21日 星期二

MahoMangaDownloaderVer10.3更新

更新完才三天左右就有使用者回報wnacg沒辦法下載, 測試了一下發現wnacg改html了, 多插了幾個class label, 我更新之前測試都還沒問題, 所以應該是這三天改版的, 我應該沒被針對吧...。



Ver10.3 更新內容:
  • 修復wnacg網站改版導致不能下載問題


下載器Demo圖:





介紹:
https://project.zmcx16.moe/?page=mahomangadownloader


環境需求



簡單除錯:
  1. 如果下載失敗, 麻煩先用瀏覽器測試看資源是否存活。
  2. 如果能正常用瀏覽器瀏覽, 麻煩先查看LogFiles資料夾內的log檔案看錯誤訊息為何。
  3. 回報問題時, 麻煩提供有問題的網址以及log內容, 這樣我才有辦法測試找問題原因。



檔案位址:
https://drive.google.com/open?id=1xchDamR4RzFXSTjvQAmyW1olxW664NSO

32位元版本:
https://drive.google.com/open?id=1H-8xA3ZAsf-1ToLmJhNs5TeXKnqrN4IS

解壓密碼:zmcx16



免責聲明:
******************
MahoMangaDownloader僅作為學術研究使用,禁止利用本程式行非法用途。

2020年4月17日 星期五

MahoMangaDownloaderVer10.2更新

前幾天有使用者反應wnacg的搜尋功能有重複問題問是不是bug, 看了一下還真的是bug, 之前讓搜尋功能除了關鍵字以外也可以支援url的地方有個地方沒改到造成, 補一下就好了~。

另外之前有個使用者寄信來問可以讓線上更新從google drive移到github release, 還有能不能支援簡體中文, 原本想說簡單做一下應該OK, 不過後來研究了一下:

1. Github private repo不支援public release, 這代表我得生個空的public repo專門放下載器的release, 後來想想這樣感覺像濫用github, 想想還是算了, 先繼續維持用google drive, 等哪天google drive有問題在移到其他地方了...。

2. 簡體中文我也不熟, 頂多只能直接機翻, 可是直接機翻感覺之後或許會有其他問題, 想了想還是先PASS了, 只支援自己熟悉的語言比較不會出些自己不能處理的怪問題...。


Ver10.2 更新內容:
  • 修復wnacg搜尋重複問題


下載器Demo圖:





介紹:
https://project.zmcx16.moe/?page=mahomangadownloader


環境需求



簡單除錯:
  1. 如果下載失敗, 麻煩先用瀏覽器測試看資源是否存活。
  2. 如果能正常用瀏覽器瀏覽, 麻煩先查看LogFiles資料夾內的log檔案看錯誤訊息為何。
  3. 回報問題時, 麻煩提供有問題的網址以及log內容, 這樣我才有辦法測試找問題原因。



檔案位址:
https://drive.google.com/open?id=1P36XfE0cHCZ0Q_XNExAQdM5dWW5AXpbj

32位元版本:
https://drive.google.com/open?id=1OuSFn3Vq9B3EenwitB_uSZ23lb_MU_yn

解壓密碼:zmcx16



免責聲明:
******************
MahoMangaDownloader僅作為學術研究使用,禁止利用本程式行非法用途。

2020年4月13日 星期一

[React] 製作了一個阿克西斯教傳教網站

前陣子在找新的side project靈感時, 偶然發現了下面這個粉絲網站:

https://satania.moe/

這網站做的真的驚為天人, 這粉絲愛已經是嘆為觀止的境界了! 看到這網站後就激起了我也想做一個粉絲網站的心情, 決定一定也要來做個粉絲網站, 最後首選主題當然就是宣傳阿克西斯教的網站了!!!  畢竟阿克西斯教的美妙我相信是目前人類社會中最需要的宗教, 如果人人都是阿克西斯教徒的話, 我相信憂鬱症或自殺患者就會像天花一樣消失在人類世界中吧!!!

最後完成品就是下面網站, 基本layout是參考上面的薩塔妮亞網站(印象太深了, 想不到更適合做粉絲網站的layout...)。 特別聲明網站本身用到了非常多美好世界的輕小說跟動畫捏他, 怕被捏他暴雷的請勿進入~。

網站位址:
https://axiscult.zmcx16.moe/

Github:
https://github.com/zmcx16/AxisCult

形象圖:



美好世界字體:
SELLY poinddt (https://forum.gamer.com.tw/C.php?bsn=46218&snA=825)

形象圖 & Misc素材繪師:
超愛喝榛奶

關於網站素材的使用以及相關權利的細節部分, 請參考這裡

P.S. 這個網站是為了介紹和推廣"為了美好世界獻上祝福"系列作品以及為了將阿克西斯教的美妙傳到世界各地所製作, 網站大多數素材皆來自"為了美好世界獻上祝福"第一、二季動畫並為美好世界製作委員會所有, 禁止將該網站素材作非法或營利使用。

<----------------------------- 我是分隔線----------------------------->

再來就是開發日誌了, 由於要做粉絲網站一定會用到動畫的圖片素材, 當然這些都是版權物, 不能隨便濫用的, 研究了下著作權法, 其中關於合理使用的部分, 內容如下:

合理使用(Fair use):
著作之利用是否合於第四十四條至第六十三條所定之合理範圍或其他合理使用之情形,應審酌一切情狀,尤應注意下列事項,以為判斷之基準:

一、利用之目的及性質,包括係為商業目的或非營利教育目的;
二、著作之性質;
三、所利用之質量及其在整個著作所占之比例;
四、利用結果對著作潛在市場與現在價值之影響。

中華民國著作權法第二十二條:
在下列情況下使用作品,可以不經著作權人許可,不向其支付報酬,但應當指明作者姓名、作品名稱,並且不得侵犯著作權人依照本法享有的其他權利:

* 為介紹、評論某一作品或者說明某一問題,在作品中適當引用他人已經發表的作品。

以我做的這個網站來說, 目的是為了介紹以及推廣"為美好世界獻上祝福"系列作品, 以及推廣阿克西斯教的美妙之處, 以目的來說是符合合理使用的, 所以有問題的部分就在於, 是不是有侵害到著作權人的權利,

1. 網站是非營利為目的, 為了推廣"阿克西斯教"的美妙到世界各地, 充分富含"教育意義", 沒有商業或營利使用在判定是合理使用上是比較站得住腳的(不過非營利不代表就是合理使用, 重點還是在有沒有損害著作權人的權益)。

2. 著作本身是介紹及推廣相關系列作品的網站, 而不是重製成新的動畫或影片, 以著作性質來說應該是OK的, 就像是介紹一本書或寫評論總需要截錄部分內容, 或是網路書店要賣書也要提供幾頁試閱才有辦法行銷書籍, 我也不可能不用任何"美好世界獻上祝福"的素材就推廣阿克西斯教, 如果我會心電感應或許還有一點點可能性...(或是只寫純文字行銷文或畫同人漫, 不過這塊也是合理使用的灰色地帶, 只是比我用動畫圖片作網站更不會有問題而已, 可是也不是說完全沒有..., 希望日本的新萬惡著作權法不要修過..., 真的會害死一堆人跟ACG產業...)。

3. 網站為了豐富內容所以使用了不少動畫的截圖, 不免有不少精彩內容得放到網站上, 像是價值萬金的阿克西斯教義, 還有推廣阿克西斯教的入教手段, 這部分的內容是美好世界第二季第8~10話的精華, 因為只有圖片跟文字所以在整個著作佔的比例極低, 可是以質量來說卻很高, 這部分可以說是最大的痛點吧, 畢竟合理使用本身就是著作權法的黑洞, 沒有合理使用的規範那社會就沒辦法進步, 畢竟隨便說個話或評論什麼就整個出局了, 有合理使用的規範才能促進社會的發展, 而保障著作權人的權利部分, 最重要的部分還是在於目的性, 比例原則, 以及是否侵害到著作權人的權利。

4. 這個網站會不會對著作潛在市場與現在價值之影響, 這部分應該可以說幾乎沒有負面影響吧, 畢竟不會有人看了我的網站後就覺得"美好世界獻上祝福"是糞作 (有這種想法的人一定會遭天譴!!!), 更不會有人看了我的網站後就覺得滿足已經不需要再去買原作看了。 這兩個原因以外就各式各樣, 例如有人用這網站得來的動畫圖片去做成周邊商品販賣等等這種完全出局的行為就100%違反著作權, 所以唯一有問題的點應該是在於, 我的網站素材會不會有被散佈導致惡意使用造成著作權人權利侵害的情況, 這不能說沒有可能, 不過基本上機會很低 (畢竟是已發布的動畫作品, 取得的管道各式各樣, Google就一大堆了...), 如果要在乎這點八成什麼也沒辦法做了就是。

這樣分析下來, 製作這網站到底有沒有符合合理使用, 坦白說這還真的只能給法官判才知道, 畢竟合理使用本來就沒有絕對的標準, 真要說唯一的標準就是著作權人想不想告你, 然後告不告得成吧。 只要著作權人覺得自己權益被侵害了, 他就可以告你, 而最後會勝訴或敗訴, 就取決於是不是符合合理使用的原則, 如果有著作權人覺得自己的作品被寫了篇負面評論文章就要告, 那是一定告不成的。 可是要是告的原因是他的作品被他人用其他形式販賣影響到自己權益, 例如散佈或私自販賣完整或部分內容, 盜版周邊商品等等, 或無正當情況損及作品價值等等, 那就是100%違反了著作權。

簡單說起來就是, 這個網站還是有違反合理使用的可能性, 就看美好世界製作委員會要不要告, 然後告不告得成這樣, 雖然保險起見不做這網站就不用怕被告, 可是為了宣揚阿克西斯教的美好到全世界, 我甘願承擔這個風險!!! 如果官方連絡我覺得我侵犯到他們權利希望我下架網站, 那我當然會照辦, 不過如果他們願意當成我是在推廣"美好世界獻上祝福"系列作品, 以及為了阿克西斯教的佈道努力而許可的話, 我會非常非常感謝的~~~!!!

<----------------------------- 我是分隔線----------------------------->

寫一大堆著作權法的分析, 再來就是技術話題了, 原本做這網站的最大動機當然就是薩塔妮亞那個粉絲網站了, 不過既然都要做網站了, 剛好我又想學React, 就乾脆邊學React邊做這網站, 總共大概花了一個多月的時間吧, 因為是邊做邊學, 所以著實的踩了不少雷, 直接用條列式來說明吧~。

1. React在16.8版之後開始推廣React Hook的開發, 完全沒想到我買的2016年出版的React教學書已經過時了..., 雖然React還是完全支持React Class的開發, 可是既然都要學了當然是學最新的, React Class跟React Function的差異還不小, 以我個人觀點來說, React Class比較好理解 & 不會踩雷(畢竟元件的生命週期的插入點都給你了), 可是React Hook比較簡潔而且開發更容易, 所以我還是比較偏好React Hook, 只是做這網站的過程踩的雷真的比我想得還多很多就是了... (沒認真把React Hook文件看完, 有些還看不懂, 這點真的蠻硬傷, 第一次的邊學邊做總是會這樣...)

2. 在寫某個圖片切換的功能時, 有加了timer讓他每幾秒自己切換圖片, 然後也加了滑鼠懸浮就停止切換, 然後點擊滑鼠也會自動切換的功能, 可是沒想到就遇到一個奇妙的現象, 如果timer切換圖片時剛好把滑鼠移進移出圖片, 那圖片切換會瞬間卡卡的..., 因為這是特效出問題跟邏輯無關, 找問題找了超久..., 後來才注意到是自己不了解React的元件更新, 你只要用UseState宣告的物件, 都會綁在React的元件週期裡, 只要狀態改變就會整個component重新render, 而因為我把圖片是否hover的狀態用useState儲存, 所以才會造成切換圖片時我只要把滑鼠移進或移出圖片, React就會在切換圖片切換到一半時強制重新render, 才造成切換圖片的特效卡卡的情況。

3. 網頁放著正常, 可是如果切到別的分頁一陣子, 在切回去會當超久才恢復正常。 這問題會發生是因為Chrome在inactive tab上, setInterval下的某些render行為是不會工作的, 可是在你切回分頁後, 就開始瘋狂工作, 原本timer做的事就只是邏輯改狀態, 照理說不會有問題, 可是我的timer改狀態的值是跟圖片切換綁在一起的, 所以變成切回分頁時timer瘋狂做事然後React也瘋狂Render切換圖片, 才會導致網頁短時間當掉, 這邊後來有找到偵測現在分頁是不是inactive, 加到切換圖片的判斷邏輯才解決這問題。

4. 這個雷是最神奇的雷, 網站本身有做RWD, 所以我會在component的實作上判斷現在是不是手機瀏覽, 如果是的話會改css style, 可是卻發生mobile判斷錯誤成desktop的問題, 而且這問題在npm run develop的時候不會發生, 可是在npm build official build成product code之後實際上線測試網站卻會出現。 後來這bug的解決方法是我把判斷mobile相關的code都搬到useEffect上才好, 可見原因應該是React component的function段的執行時間跟React真正的component mount並不一樣, 直接在function段上偵測mobile會出問題(可是我寫log卻是判斷正確的, 超玄...), 雖然分析起來應該是這樣, 可是為什麼develop不會出現這問題, 直到真的build product code才發生, 這個我真的不懂..., 也google不到原因, 後來真的是運氣好自己亂試試出來的, 有人知道為什麼的希望能告訴我...。

5. 基本上跟上面的4的雷差不多, React的元件生命週期基本上是跟狀態綁定一起的, 用UseState創建的物件只要狀態改變, React就會重新Render這個component, 可是有時候我做一個Component會有許多物件, 有些物件不想每次重新Render就會用UseRef建構, 可是要是你這個元件是跟其他外部的component狀態有dependency, 例如像是某個語系改變的話你會連動改各個component的css style, 那會變成外部元件偵測語系改變了, 然後有用到這些語系資料的component React也自動幫你重新Render了, 可是不是用UseState創建的物件卻不會被更新到, 這也是React官方表明盡量使用UseState管理你的物件, 不然就是會有各種狀態非預期的雷...。 要解決這問題我目前想到大概就兩種, 最正統作法就是一個component下有些物件你不想每次都Render, 那就乖乖全部做成sub component, 這樣比起寫成一個大的component又用UseRef的情況踩到雷的機會會小的多; 而第二種作法就很單純, 遇到雷就再解決吧, 用useEffect管理你其他沒有useState的元件, 不過這種作法小component還好, 要是規模一大你一定會想哭...。

雖然學React還學沒多久, 踩到的雷卻比我想像得還多, 不過實際體驗下來, 真的有刷新三觀的感覺, 就像以前學js的過程摸到jquery後就再也回不去了一樣, component的開發方式讓我自己有脫離網頁義大利麵條式開發的感覺(麻...一部分原因也是因為我寫的都是小網站), 雖然自己前端真的是興趣學爽的, 沒有想特別深入往前端走(寫前端真的好苦力..., 還是喜歡後端開發), 不過因為自己比起寫library或command line tool, 還是喜歡做整套小專案, 以後應該還是會隨心所欲, 想玩什麼就摸什麼吧~。