整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          調試 LNK1123 轉換到 COFF 期間失敗:文

          調試 LNK1123 轉換到 COFF 期間失敗:文件無效或損壞(上)

          前一段時間在折騰拆分 rc 的問題,已經把遇到的問題整理成文了。感興趣的小伙伴兒可以參考這里,這里 和 這里。本以為不會有問題了,后續流程就請其它同事幫忙處理了,沒想到在拆分實際項目時遇到了一個非常奇怪的鏈接問題。

          本文總結了使用 process monitor 監聽進程創建,查看進程參數、使用 gflags 設置 Image File Excution Options、使用 IDA 靜態分析相關函數的業務邏輯以及使用 windbg 進行動態調試的整個過程。我認為這是一個由不良的編程習慣與 crt 的限制共同導致的問題。快來一起看看吧。

          初聞錯誤

          前些日子,在家隔離辦公的某日中午,收到同事發來的信息說 rc 拆分的編譯問題已經解決了,但是遇到了鏈接錯誤,還發送了鏈接錯誤的截圖,并且給出了一個解決方案。

          嘗試把 .rc 文件排除幾十個就鏈接過去了。

          聽到這個問題的時候,我懷疑是不是哪里操作有問題。從錯誤提示看是 無法打開 xxx.res 進行讀取,所以第一感覺是文件路徑不對。于是趕緊跟同事聊了一下,同事覺得是 vs 的限制,可能這個限制數量是 512

          但是我從沒聽過同一個工程中的 .rc 文件有數量限制,不管怎樣,還是建個簡單的工程驗證下吧。

          嘗試重現

          帶著懷疑 + 好奇的心態,我快速新建了一個 MFC 對話框工程。然后在 vs 中不斷復制默認對話框(大概復制了600 個,已經比同事所說的 512 上限要多了,如果有問題應該能重現了),然后使用工具把每個對話框拆分成獨立的 .rc 文件并添加到工程文件中。保存好工程后,開始編譯。等待一段時間后,果然報錯了,錯誤截圖如下:

          從錯誤提示看,處理 dialog_testmultiplerccompile_dialog507.rc 文件的時候報錯了。按照同事說的,刪除若干個 .rc 文件,只保留 500 個,再次編譯,沒有報錯。

          看來,在同一個工程中包含太多 .rc 文件真可能有問題。難道真有限制?為什么會做這種限制呢?不管為什么要做限制,我需要找到一個解決方案。

          開始深入調查前,先看看報錯信息。

          熟悉的錯誤

          之前遇到過錯誤 LINK : fatal error LNK1123: 轉換到 COFF 期間失敗: 文件無效或損壞,是由于 link.execvtres.exe 的版本不一樣導致的。這次報錯不是這個原因。通過 process monitor 看,這兩個程序的路徑是一樣的。

          再看錯誤 error CVT1101: 無法打開“dialog_testmultiplerccompile_dialog507.res”進行讀取。猜測是在讀取這個文件的時候發生了錯誤,可以在 process monitor 中查看相關事件。

          過濾相關事件

          process monitor 中根據路徑名進行過濾。如果路徑以 dialog_testmultiplerccompile_dialog507.res 結尾則包含,如下圖:

          沒想到一條記錄都沒有,一片空白。這是怎么回事?說實話,我有點不知所措,看來只能硬著頭皮調試 + 用 IDA 逆向了。在調試之前,先用 IDA 看看有沒有什么發現。

          請出 IDA

          使用 ida32 打開 cvtres.exeIDA 會提示是否查找符號(真是一個好消息),當然選擇是。等待 IDA 分析完成后,在左側的 Function window 中找到 _main,雙擊查看反匯編代碼,直接在反匯編窗口按 F5,查看偽代碼( IDAF5 真香!)。

          大概瀏覽后,基本明白了 main() 函數的整體流程。首先,解析傳入的參數,確定第一個文件在參數列表中的索引位置。然后,從此索引開始循環調用 ReadResFile() 讀取每個文件,讀取完所有的文件后統一調用 CvtRes() 函數進行轉換。

          下圖是在 IDA 中對 main() 函數使用 F5 獲得的偽代碼的后半部分。

          其中的 CvtRes() 函數應該是轉換的主要函數,非常值得懷疑。迫不及待的啟動 windbg 準備調試,但是 cvtres.exe 是被 link.exe 調用的,該如何調試呢?

          搭建調試環境

          如果 cvtres.exe 啟動的時候,能夠自動中斷到調試器中,就可以方便的調試了。之前在 全局變量初始化順序探究 中介紹過使用 gflags 進行設置的方法。

          根據之前調試 cl.exe 的經驗,如果長時間中斷到調試器中,調用者會重新啟動 cl.exe。猜想這里也會有類似的邏輯。為了避免這種問題,需要根據 link.exe 啟動 cvtres.exe 的參數手動運行 cvtres.exe

          可以通過 process monitor 很快找出 cvtres.exe 需要的參數。經過簡單觀察,發現傳遞給 cvtres.exe 的參數比較簡單直接,而且根據 cvtres.exe /? 提供的幫助信息,可以很快確定各個參數的意義。

          于是很快寫出了一個批處理腳本,如下圖:

          沒想到,雙擊腳本運行的時候,出現了如下錯誤:

          提示找不到 cvtres.exe。看來需要使用完整路徑。正確的腳本如下:

          說明: 為了避免命令行參數過長,我特意簡化了 .res 文件名,之前的名字太長了。而且經過測試,打開 510.res 的時候就能重現,沒必要準備 600 多個 .res 進行測試,這里只準備了 511.res 文件進行測試。

          猜錯了

          雙擊腳本啟動 cvtres.exe,立刻就中斷到了 windbg 中。

          windbg 中執行 x cvtres!*main 即可找到入口函數,輸入 bp cvtres!wmain 即可在 wmain() 函數入口處設置好斷點。

          同理,執行 x cvtres!*CvtRes 即可找到 cvtres!CvtRes() 函數,輸入 bp cvtres!CvtRes 即可在 CvtRes() 函數入口處設置好斷點。

          設置好斷點后,輸入 g 讓程序跑起來,可以發現 wmain() 函數內的斷點命中了,但是 CvtRes() 函數內的斷點并沒有命中,進程直接退出了。

          有些出乎意料,居然不是在 CvtRes() 函數里出的錯。沒(有)關(點)系(懵),繼續挖掘有效信息。

          繼續努力

          雖然進程退出了,但是依然可以通過 k 系列命令查看調用棧,在 windbg 中輸入 kp,如下圖:

          上圖中紅色高亮部分就是關鍵調用棧。從上圖還可以得到一個非常有用的信息 —— exit code 的值是 1。可以猜測,link.exe 就是根據 cvtres.exe 的返回值來判斷其是否執行成功的。

          調用棧中的 OurFileOpen() 函數,應該是負責打開文件的函數。在繼續調試之前,先在 IDA 中看看 OurFileOpen() 函數的實現。

          回到 IDA

          雙擊 OurFileOpen,當然是直接查看 F5 的結果啦,有細節需要確認再看反匯編代碼。

          可以看到這個函數實現的非常簡單,就是調用 _wfsopen(),如果失敗(result==0)那么調用 ErrorPrint() 打印錯誤信息。如果 open_mode(第二個參數)是 0,那么傳遞給 ErrorPrint() 的第一個參數是 1101,否則是 1108

          而調用 OurFileOpen 時傳遞的第二個參數是通過 edx 傳遞的,對應的值是 0,所以如果出錯,那么會傳遞 1101

          說實話,看到 OurOpenFile() 函數中的 1101 ,我太激動了,因為在vs 中看到的錯誤提示是 error CVT1101: 無法打開“xxx.res”進行讀取。為了進一步確認猜想,在 IDA 中查看 ErrorPrint() 函數的反匯編代碼,如下圖:

          從上方紅色高亮語句 CVTRES: fatal error CVT%04u: 基本可以確定猜測是正確的。從上圖底部的紅色高亮區域還可以知道該函數內部確實會調用 exit(1) 來結束進程。

          接下來需要調查的問題是 _wfsopen 為什么失敗了?

          為什么 _wfsopen 會失敗?

          windbg 中輸入 .restart 重啟目標程序,輸入 bp MSVCR120!_wfsopen,然后執行 g 命令。因為已經設置好了符號查找路徑,所以 windbg 自動打開了對應的源碼文件。

          這個函數雖然很簡單,加上注釋不到 50 行。但是會被調用很多次,根據經驗,前面的 500 多次調用都沒有問題,在嘗試打開 510.res 的時候會有問題,所以設置一個條件斷點非常有必要。

          簡單查看反匯編代碼發現,_wfsopen() 函數的第一個參數是通過 ecx 傳遞的,可以設置如下的條件斷點(真是燒腦還不好理解,我不會告訴你,我嘗試了很久才寫出了下面這段蹩腳的腳本):

          bp MSVCR120!_wfsopen "aS /mu $myFileName @ecx; .block {.echo $myFileName; r @$t0=$spat(@\"$myFileName\", @\"*510.res\"); .if(1==$t0){.echo **** bang ****} .else{ gc;} };"

          耐心等待一會就中斷下來了,如下圖:

          單步走兩步,發現是 _getstream() 出錯了。

          _getstream 錯在哪里了?

          輸入 .restart 重啟目標程序,并且設置好條件斷點,重新運行程序,當中斷到 _wfsopen() 函數后,單步步入到 _getstream() 函數中。

          可以看到 _getstream() 函數邏輯也不復雜,根據注釋可以很簡單的理解此函數的邏輯 —— 從 __piob 中(大小是 _nstream,通過 dt _nstream 可知其大小是 512)找到一條可用的記錄項。判斷一條記錄項是否可用的標準是 __piob[i]==NULL ,或者 !inuse( (FILE *)__piob[i] ) && !str_locked( (FILE *)__piob[i] )

          直接在函數末尾加好斷點,g 起來,發現確實沒有找到一條可用的記錄項。

          至此,我大概明白了整個過程。cvtres.exemain() 函數中會循環調用 ReadResFile() 函數(內部會調用 _wfsopen())讀取所有的 .res 文件,但是讀取完一個 .res 文件后,并沒有關閉,當打開一定數量的文件后會導致 __piob 被占滿。再嘗試打開一個文件的時候就報錯了。

          看來,crt 還有最大打開文件數的限制,趕緊 google 搜索是否有什么設置可以調整最大文件打開數量。

          google 一下

          google 中輸入 crt max open file 找到了幾個相關的網址。

          雖然可以通過 _setmaxstdio() 調整 crt 的最大文件打開數,但是好像不能通過修改配置文件或者修改注冊表的方式調整。

          發帖詢問

          說實話,第一次分析到這個結果的時候我是有些不信的。于是我再三確認了 ReadResFile() 函數內部確實沒有關閉文件的操作。難道有什么特殊的理由不關閉打開的文件?但是我實在想不出有什么理由。所以我覺得這是一個 bug,于是我在微軟官方論壇上發了一個帖子,希望能得到一些回復。

          帖子地址是 https://docs.microsoft.com/en-us/answers/questions/709392/cvt1101-can39t-open-xxxres-for-reading.html

          目前只有一位網友回復(另外一個是我自己),為了方便大家閱讀,截圖如下:

          雖然到現在還沒收到官方的確認回復,不過我依然認為這是一個 bug,而不是 feature

          解決方案

          既然沒有設置選項或者配置文件可以簡單的調整最大文件打開數量,對 cvtres.exe 打補丁又不太現實(每臺機器上都要做處理),等待微軟修復這個問題也不現實(遠水解不了近渴)。所以我們的解決方案是通過合并一些 .rc 以減少工程中的 .rc 文件數量來規避這個問題。

          雖然問題已經調查清楚了,但是還有幾個問題值得探究。

          幾個值得深究的問題

          1. 為什么鏈接的時候需要調用 cvtres.exe 呢?
          2. 有沒有更好的設置條件斷點的方式?目前的語法實在是太難用了。
          3. 有什么簡單的辦法可以查看 __piob 數組中元素的內容嗎?
          4. 為什么在打開 510.res 的時候就報錯了?應該可以打開 512 個文件才對?

          由于本篇已經太長了,下一篇文章中繼續把殘留的這幾個問題解答。

          總結

          • crt 有最大打開文件數的限制,可以通過 _setmaxstdio() 進行調整。
          • 在一個工程中最好不要同時包含太多 .rc 文件,一般應該不會遇到我遇到的這種情況。
          • 在不需要使用文件的時候,一定要及時關閉。
          • 進程退出后,依然可以使用 k 系列命令查看調用棧,有時候可以快速定位進程退出的原因。

          參考資料

          https://stackoverflow.com/questions/61581826/visual-studio-2019-cvt1101-lnk1123-fatal-error

          https://docs.microsoft.com/en-us/cpp/build/reference/dot-res-files-as-linker-input?view=msvc-170

          https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/setmaxstdio?view=msvc-170

          vs2013 自帶的 crt 源碼

          多人都說HTML是一門很簡單的語言,看看書,看看視頻就能讀懂。但是,如果你完全沒有接觸過,就想通過看一遍教程,背背標簽,想要完全了解HTML,真的有點太天真了。

          HTML中文“超文本標記語言”,英文名叫HTML沒有變量,沒有循環,沒有函數,只是單純的一門靜態語言而已。你可以用來描述靜態的東西,比如標題、段落、圖片。

          1)HTML通常被稱為靜態網頁

          2)HTML的一些標簽代碼規則將內容呈現在瀏覽器中所需的風格。

          3)HTML可以使用記事本創建,并以.html為擴展名保存。

          打開瀏覽器,例如打開百度的首頁

          這個頁面非常的簡潔,但是包含了很多內容,有文字、圖片、動畫、超鏈接等一系列HTML頁面所能夠包含的元素。什么意思,也就是說,HTML頁面就是能夠包含文本、圖像、聲音、超鏈接等內容的集合,然后通過瀏覽器對這些元素進行渲染,就呈現出多彩的頁面。

          打開頁面的審查元素(快捷鍵是【F12】),就能夠看到構成HTML頁面的所有元素,當我們在寫頁面,對頁面進行調試的時候,也是通過審查元素,在這個窗口里面檢測問題,所以審查的方法一定要掌握。


          一個HTML頁面最基本框架。

          優點

          易于使用,松散的語法(雖然,過于靈活的將不符合標準),HTML還允許使用模板,這使設計網頁變得容易、對Web設計領域的初學者來說非常有用。幾乎所有瀏覽器都支持該功能。被廣泛使用的; 建立在幾乎所有網站上。與XML語法非常相似,后者已越來越多地用于數據存儲。免費-無需購買任何軟件,即使對于新手程序員而言,都易于學習和編碼。

          缺點

          由于它是一種靜態語言,它不能單獨產生動態輸出。有時,HTML文檔的結構難以掌握。程序錯誤可能會導致高昂的代價。它只能創建靜態頁面和普通頁面,因此如果我們需要動態頁面,則HTML無效。需要編寫大量代碼來制作簡單的網頁。您必須跟上已棄用的標記,并確保不要使用它們,因為出現了另一種與HTML兼容的語言代替了標記的原始工作。因此需要學習其他語言(大多數情況下是CSS)HTML提供的安全功能受到限制。

          在了解這么多之后,一定想要自己寫個HTML頁面試試手,那么HTML頁面怎么寫呢,用什么工具來寫呢?在電腦上建立一個hello.txt的文件,將下面的代碼粘貼復制進去保存 。

          然后將后綴名修改為.html,用瀏覽器在頁面上看到hello world字樣的輸出,這就是第一個HTML頁面

          今天我們就先分享到這里啦,趕快去練練手吧~(私信我有免費IT課程可以領取喲)

          tmlAgilityPack 是一個 HTML 解析庫,用于 .NET 平臺。它允許開發者以類似于解析 XML 的方式,輕松地解析和操作 HTML 文檔。這個庫特別適合處理非標準的 HTML,例如那些格式不正確或包含錯誤的 HTML 文檔。

          從原理上說,解析是一個 CPU 密集型操作。在計算資源充裕的情況下,使用多線程并行可以加快處理速度。

          以下代碼展示了兩個場景:

          • 使用一個線程解析 1000 個頁面

          • 使用 8 個線程解析 1000 個頁面(總量 1000 個,測試機器上的 CPU 有 8 個內核)。

          • string html=File.ReadAllText("PATH");//One threadfor (int i=0; i < 1000; i++) new HtmlDocument().LoadHtml(html);//Several threadsParallel.For(0, 1000, (int i)=> new HtmlDocument().LoadHtml(html));

            然而實際的情況是:盡管多線程版本消耗了 2 ~ 3 倍的 CPU,但所花費的時間大致相同。而且 CPU 占用率一直維持在 30% 以下。即便更換了要處理的頁面,或者內核數量更多的電腦,情況都差不多。

            如何解決

            開啟更多的線程并不會提升處理的速度,這讓我開始懷疑是不是存在鎖的問題。遺憾的是沒有在源代碼中找到 lock ,但是發現了一個 Issues:

            https://github.com/zzzprojects/html-agility-pack/issues/191

            在使用 Profiler 工具對多線程程序進行分析之后,發現程序可能存在內存瓶頸。根據他的觀察,有大約 50% 的 CPU 時間耗費在了內存分配上。

            這和使用的 GC 類型有關,向 App.config 增加以下代碼可以解決該問題:

            •  <runtime> <gcServer enabled="true"/> <gcConcurrent enabled="false" /> </runtime>

              適配 .NET 8.0

              我的程序是一個使用 .NET 8.0 框架的控制臺,增加 App.config 文件之后并沒有效果。于是,我找到了微軟的官方文檔:

              https://learn.microsoft.com/zh-cn/dotnet/core/runtime-config/garbage-collector

              根據文檔所述,可以通過環境變量、runtimeconfig.json 文件或項目文件來指定程序使用 Server 版本。我選擇修改項目文件:

              • <Project Sdk="Microsoft.NET.Sdk">
                <PropertyGroup> <ServerGarbageCollection>true</ServerGarbageCollection> </PropertyGroup>
                </Project>

                問題得以解決:處理速度快了不少,CPU 占用維持在了 70% 左右。


          主站蜘蛛池模板: 国产成人精品一区二区秒拍| 国产精品一区在线观看你懂的| 亚洲欧美日韩一区二区三区在线| 中文字幕一区二区三区在线观看| 亚洲国产精品综合一区在线 | 亚洲欧美一区二区三区日产 | 国产成人一区二区动漫精品| 国产精品亚洲一区二区在线观看| 视频一区二区在线播放| 视频一区二区三区在线观看| 精品亚洲一区二区三区在线播放| 综合人妻久久一区二区精品| 久久人做人爽一区二区三区| 欧洲精品码一区二区三区| 区三区激情福利综合中文字幕在线一区亚洲视频1 | 一区二区三区影院| 国产午夜精品免费一区二区三区 | 极品尤物一区二区三区| 国产伦精品一区二区三区免.费 | 国产精品亚洲一区二区在线观看 | 国产爆乳无码一区二区麻豆| 亚洲欧洲日韩国产一区二区三区| 国产Av一区二区精品久久| 爆乳熟妇一区二区三区霸乳| 人妻体内射精一区二区三区| 国产婷婷色一区二区三区深爱网 | 亚洲电影一区二区三区| 日本高清天码一区在线播放| 国产综合精品一区二区三区| 国产乱人伦精品一区二区在线观看| 亚洲国产专区一区| 中文字幕无码不卡一区二区三区| 搜日本一区二区三区免费高清视频| 久久一本一区二区三区| 精品国产免费一区二区| 99精品国产一区二区三区不卡| 内射少妇一区27P| 国产福利微拍精品一区二区| 在线观看国产一区| 国产精品成人一区无码| 精品人妻码一区二区三区 |