首页 > 其他分享 >使用 IDA 和 windbg 调试 LNK1123 转换到 COFF 期间失败:文件无效或损坏(上)

使用 IDA 和 windbg 调试 LNK1123 转换到 COFF 期间失败:文件无效或损坏(上)

时间:2022-12-20 22:07:56浏览次数:69  
标签:exe windbg res cvtres rc IDA LNK1123 COFF 函数

使用 IDA 和 windbg 调试 LNK1123 转换到 COFF 期间失败:文件无效或损坏(上)

原总结排错process monitorvsIDAwindbg调试rcCVT1101LNK1123

缘起

前一段时间在折腾拆分 ​​rc​​ 的问题,已经把遇到的问题整理成文了。感兴趣的小伙伴儿可以参考​​这里​​​,​​这里​​​ 和 ​​这里​​​。本以为不会有问题了,后续流程就请其它同事帮忙处理了,没想到在拆分实际项目时遇到了一个非常奇怪的链接问题。本文总结了使用 ​​process monitor​​ 监听进程创建,查看进程参数、使用 ​​gflags​​ 设置 ​​Image File Excution Options​​、使用 ​​IDA​​ 静态分析相关函数的业务逻辑以及使用 ​​windbg​​ 进行动态调试的整个过程。我认为这是一个由不良的编程习惯与 ​​crt​

初闻错误

前些日子,在家隔离办公的某日中午,收到同事发来的信息说 ​​rc​

使用 IDA 和 windbg 调试 LNK1123 转换到 COFF 期间失败:文件无效或损坏(上)_调用栈

尝试把 .rc

听到这个问题的时候,我怀疑是不是哪里操作有问题。从错误提示看是 ​​无法打开 xxx.res 进行读取​​,所以第一感觉是文件路径不对。于是赶紧跟同事聊了一下,同事觉得是 ​​vs​​ 的限制,可能这个限制数量是 ​​512​​但是我从没听过同一个工程中的 ​​.rc​

尝试重现

带着怀疑 + 好奇的心态,我快速新建了一个 ​​MFC​​ 对话框工程。然后在 ​​vs​​ 中不断复制默认对话框(大概复制了​​600​​ 个,已经比同事所说的 ​​512​​ 上限要多了,如果有问题应该能重现了),然后使用工具把每个对话框拆分成独立的 ​​.rc​

使用 IDA 和 windbg 调试 LNK1123 转换到 COFF 期间失败:文件无效或损坏(上)_调用栈_02

cvt1101-lnk1123-in-test-project

从错误提示看,处理 ​​dialog_testmultiplerccompile_dialog507.rc​​ 文件的时候报错了。按照同事说的,删除若干个 ​​.rc​​ 文件,只保留 ​​500​​看来,在同一个工程中包含太多 ​​.rc​

开始深入调查前,先看看报错信息。

熟悉的错误

之前遇到过错误 ​​LINK : fatal error LNK1123: 转换到 COFF 期间失败: 文件无效或损坏​​,是由于 ​​link.exe​​ 与 ​​cvtres.exe​​ 的版本不一样导致的。这次报错不是这个原因。通过 ​​process monitor​

使用 IDA 和 windbg 调试 LNK1123 转换到 COFF 期间失败:文件无效或损坏(上)_调用栈_03

link-cvtres-same-path

再看错误 ​​error CVT1101: 无法打开“dialog_testmultiplerccompile_dialog507.res”进行读取​​。猜测是在读取这个文件的时候发生了错误,可以在 ​​process monitor​

过滤相关事件

在 ​​process monitor​​ 中根据路径名进行过滤。如果路径以 ​​dialog_testmultiplerccompile_dialog507.res​

使用 IDA 和 windbg 调试 LNK1123 转换到 COFF 期间失败:文件无效或损坏(上)_反汇编_04

include-path-end-with-dialog507

没想到一条记录都没有,一片空白。这是怎么回事?说实话,我有点不知所措,看来只能硬着头皮调试 + 用 ​​IDA​​ 逆向了。在调试之前,先用 ​​IDA​

请出 IDA

使用 ​​ida32​​ 打开 ​​cvtres.exe​​,​​IDA​​ 会提示是否查找符号(真是一个好消息),当然选择是。等待 ​​IDA​​ 分析完成后,在左侧的 ​​Function window​​ 中找到 ​​_main​​,双击查看反汇编代码,直接在反汇编窗口按 ​​F5​​,查看伪代码( ​​IDA​​ 的 ​​F5​​大概浏览后,基本明白了 ​​main()​​ 函数的整体流程。首先,解析传入的参数,确定第一个文件在参数列表中的索引位置。然后,从此索引开始循环调用 ​​ReadResFile()​​ 读取每个文件,读取完所有的文件后统一调用 ​​CvtRes()​​下图是在 ​​IDA​​ 中对 ​​main()​​ 函数使用 ​​F5​

使用 IDA 和 windbg 调试 LNK1123 转换到 COFF 期间失败:文件无效或损坏(上)_解决方案_05

cvtres-main-function-logic

其中的 ​​CvtRes()​​ 函数应该是转换的主要函数,非常值得怀疑。迫不及待的启动 ​​windbg​​ 准备调试,但是 ​​cvtres.exe​​ 是被 ​​link.exe​

搭建调试环境

如果 ​​cvtres.exe​​ 启动的时候,能够自动中断到调试器中,就可以方便的调试了。之前在 ​​​全局变量初始化顺序探究​​​ 中介绍过使用 ​​gflags​

使用 IDA 和 windbg 调试 LNK1123 转换到 COFF 期间失败:文件无效或损坏(上)_调用栈_06

gflags-cvtres-setting

根据之前调试 ​​cl.exe​​ 的经验,如果长时间中断到调试器中,调用者会重新启动 ​​cl.exe​​。猜想这里也会有类似的逻辑。为了避免这种问题,需要根据 ​​link.exe​​ 启动 ​​cvtres.exe​​ 的参数手动运行 ​​cvtres.exe​​。可以通过 ​​process monitor​​ 很快找出 ​​cvtres.exe​​ 需要的参数。经过简单观察,发现传递给 ​​cvtres.exe​​ 的参数比较简单直接,而且根据 ​​cvtres.exe /?​

于是很快写出了一个批处理脚本,如下图:

使用 IDA 和 windbg 调试 LNK1123 转换到 COFF 期间失败:文件无效或损坏(上)_解决方案_07

cvtres-error-startup-bat

没想到,双击脚本运行的时候,出现了如下错误:

使用 IDA 和 windbg 调试 LNK1123 转换到 COFF 期间失败:文件无效或损坏(上)_调用栈_08

windbg-cannot-start-cvtres-error

提示找不到 ​​cvtres.exe​​。看来需要使用完整路径。正确的脚本如下:

使用 IDA 和 windbg 调试 LNK1123 转换到 COFF 期间失败:文件无效或损坏(上)_解决方案_09

cvtres-startup-command

说明: 为了避免命令行参数过长,我特意简化了 .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​​,如下图:

使用 IDA 和 windbg 调试 LNK1123 转换到 COFF 期间失败:文件无效或损坏(上)_解决方案_10

cvtres-exit-call-stack

上图中红色高亮部分就是关键调用栈。从上图还可以得到一个非常有用的信息 —— ​​exit code​​ 的值是 ​​1​​。可以猜测,​​link.exe​​ 就是根据 ​​cvtres.exe​​调用栈中的 ​​OurFileOpen()​​ 函数,应该是负责打开文件的函数。在继续调试之前,先在 ​​IDA​​ 中看看 ​​OurFileOpen()​

回到 IDA

双击 ​​OurFileOpen​​,当然是直接查看 ​​F5​

使用 IDA 和 windbg 调试 LNK1123 转换到 COFF 期间失败:文件无效或损坏(上)_调用栈_11

view-ourfileopen-in-ida-using-f5

可以看到这个函数实现的非常简单,就是调用 ​​_wfsopen()​​,如果失败(​​result == 0​​)那么调用 ​​ErrorPrint()​​ 打印错误信息。如果 ​​open_mode​​(第二个参数)是 ​​0​​,那么传递给 ​​ErrorPrint()​​ 的第一个参数是 ​​1101​​,否则是 ​​1108​​。而调用 ​​OurFileOpen​​ 时传递的第二个参数是通过 ​​edx​​ 传递的,对应的值是 ​​0​​,所以如果出错,那么会传递 ​​1101​​。

使用 IDA 和 windbg 调试 LNK1123 转换到 COFF 期间失败:文件无效或损坏(上)_调用栈_12

view-ourfileopen-param

说实话,看到 ​​OurOpenFile()​​ 函数中的 ​​1101​​ ,我太激动了,因为在​​vs​​ 中看到的错误提示是 ​​error CVT1101: 无法打开“xxx.res”进行读取​​。为了进一步确认猜想,在 ​​IDA​​ 中查看 ​​ErrorPrint()​

使用 IDA 和 windbg 调试 LNK1123 转换到 COFF 期间失败:文件无效或损坏(上)_调用栈_13

view-ErrorPrint-in-ida

从上方红色高亮语句 ​​CVTRES: fatal error CVT%04u:​​ 基本可以确定猜测是正确的。从上图底部的红色高亮区域还可以知道该函数内部确实会调用 ​​exit(1)​​接下来需要调查的问题是 ​​_wfsopen​

为什么 _wfsopen 会失败?

在 ​​windbg​​ 中输入 ​​.restart​​ 重启目标程序,输入 ​​bp MSVCR120!_wfsopen​​,然后执行 ​​g​​ 命令。因为已经设置好了符号查找路径,所以 ​​windbg​

使用 IDA 和 windbg 调试 LNK1123 转换到 COFF 期间失败:文件无效或损坏(上)_反汇编_14

break-and-open-source-file

这个函数虽然很简单,加上注释不到 ​​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;} };"

耐心等待一会就中断下来了,如下图:

使用 IDA 和 windbg 调试 LNK1123 转换到 COFF 期间失败:文件无效或损坏(上)_调用栈_15

break-at-open-510res

单步走两步,发现是 ​​_getstream()​

_getstream 错在哪里了?

输入 ​​.restart​​ 重启目标程序,并且设置好条件断点,重新运行程序,当中断到 ​​_wfsopen()​​ 函数后,单步步入到 ​​_getstream()​

使用 IDA 和 windbg 调试 LNK1123 转换到 COFF 期间失败:文件无效或损坏(上)_调用栈_16

view-getstream-in-windbg

可以看到 ​​_getstream()​​ 函数逻辑也不复杂,根据注释可以很简单的理解此函数的逻辑 —— 从 ​​__piob​​ 中(大小是 ​​_nstream​​,通过 ​​dt _nstream​​ 可知其大小是 ​​512​​)找到一条可用的记录项。判断一条记录项是否可用的标准是 ​​__piob[i] == NULL​​ ,或者 ​​!inuse( (FILE *)__piob[i] ) && !str_locked( (FILE *)__piob[i] )​​。直接在函数末尾加好断点,​​g​​至此,我大概明白了整个过程。​​cvtres.exe​​ 在 ​​main()​​ 函数中会循环调用 ​​ReadResFile()​​ 函数(内部会调用 ​​_wfsopen()​​)读取所有的 ​​.res​​ 文件,但是读取完一个 ​​.res​​ 文件后,并没有关闭,当打开一定数量的文件后会导致 ​​__piob​​看来,​​crt​​ 还有最大打开文件数的限制,赶紧 ​​google​

google 一下

在 ​​google​​ 中输入 ​​crt max open file​

使用 IDA 和 windbg 调试 LNK1123 转换到 COFF 期间失败:文件无效或损坏(上)_调用栈_17

search-crt-max-open-file-in-google

虽然可以通过 ​​_setmaxstdio()​​​ 调整 ​​crt​

发帖询问

说实话,第一次分析到这个结果的时候我是有些不信的。于是我再三确认了 ​​ReadResFile()​​ 函数内部确实没有关闭文件的操作。难道有什么特殊的理由不关闭打开的文件?但是我实在想不出有什么理由。所以我觉得这是一个 ​​bug​​,于是我在微软官方论坛上发了一个帖子,希望能得到一些回复。

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

目前只有一位网友回复(另外一个是我自己),为了方便大家阅读,截图如下:

使用 IDA 和 windbg 调试 LNK1123 转换到 COFF 期间失败:文件无效或损坏(上)_解决方案_18

talking-thread-on-microsoft-q&a

虽然到现在还没收到官方的确认回复,不过我依然认为这是一个 ​​bug​​,而不是 ​​feature​​。

解决方案

既然没有设置选项或者配置文件可以简单的调整最大文件打开数量,对 ​​cvtres.exe​​ 打补丁又不太现实(每台机器上都要做处理),等待微软修复这个问题也不现实(远水解不了近渴)。所以我们的解决方案是通过合并一些 ​​.rc​​ 以减少工程中的 ​​.rc​

虽然问题已经调查清楚了,但是还有几个问题值得探究。

几个值得深究的问题

  1. 为什么链接的时候需要调用 cvtres.exe 呢?
  2. 有没有更好的设置条件断点的方式?目前的语法实在是太难用了。

有什么简单的办法可以查看 __piob​​为什么在打开 510.res

由于本篇已经太长了,下一篇文章中继续把残留的这几个问题解答。

总结

​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​

欢迎各位小伙伴指出不足,提出建议!感谢关注我的博客:)

作 者:​​编程难​​

码云博客:​​https://bianchengnan.gitee.io​

github博客:​​https://bianchengnan.github.io​

版权所有,转载请保留原文链接:)



标签:exe,windbg,res,cvtres,rc,IDA,LNK1123,COFF,函数
From: https://blog.51cto.com/u_15469822/5956820

相关文章

  • 使用 IDA 和 windbg 调试 LNK1123 转换到 COFF 期间失败:文件无效或损坏(下)
    使用IDA和windbg调试LNK1123转换到COFF期间失败:文件无效或损坏(下)原总结排错processmonitorvsIDAwindbg调试rcCVT1101LNK1123前言在前面两篇文章中(​​这里​​​,......
  • 使用 IDA 和 windbg 调试 LNK1123 转换到 COFF 期间失败:文件无效或损坏(中)
    使用IDA和windbg调试LNK1123转换到COFF期间失败:文件无效或损坏(中)原总结排错processmonitorvsIDAwindbg调试rcCVT1101LNK1123前言在​​上一篇文章​​​中,我们......
  • 使用Windbg找出程序CPU高问题
    使用Windbg找出程序CPU高问题ReggieDing系统架构师​关注 1人赞同了该文章背景本人在把应用程序部署到服务器上运行,观察一段时间后运行平稳,CPU......
  • Windbg提示:*** WARNING: Unable to verify checksum for 的处理
    当我们用windbg调试时,经常会遇到“***WARNING:Unabletoverifychecksumforxxx.dll”这样的提示,他的意思时不能校验某某模块的校验和。这一般都是我们的动态库或exe的......
  • windbg preview下载及其历史版本下载
    WinDBG是专门针对WindowsNT系列操作系统而设计的调试器。WinDBG的最初版本是微软公司在开发最初WindowsNT操作系统(NT3.1)期间推出的,它是当时NT团队内部开发和调试NT操作系......
  • windbg 分析 32 位进程的 64 位转储文件
    场景:x86的项目在x64的windows机器上运行时出现未响应的情况,使用任务管理器创建该进程的转储文件因为项目是32位的,所以使用x86的windbg来调试dmp文件,使用kn......
  • Windbg命令大全
    Windbg是在windows平台下,强大的用户态和内核态调试工具。相比较于VisualStudio,它是一个轻量级的调试工具,所谓轻量级指的是它的安装文件大小较小,但是其调试功能,却比VS更为......
  • WinDBG详解进程初始化dll是如何加载的
    一:背景1.讲故事有朋友咨询个问题,他每次在调试WinDbg的时候,进程初始化断点之前都会有一些dll加载到进程中,比如下面这样:Microsoft(R)WindowsDebuggerVersion10.0.252......
  • windbg调试kvm windows虚机
    参考:http://m.blog.chinaunix.net/uid-22954220-id-4733247.html准备2台虚机,一台target目标机,用于运行要调试的windows系统(调试内核),一台debug调试机,用于运行windbg,配置......
  • windbg下看系统非分页内存
       这篇文章实在是闲的无聊才写的,因为快过年了...文章基于xpsp3   先看看和非分页内存相关的全局变量,也好有个大局观:kd>xnt!MmNonPaged*805517d8nt!Mm......