使用 IDA 和 windbg 调试 LNK1123 转换到 COFF 期间失败:文件无效或损坏(中)
使用 IDA 和 windbg 调试 LNK1123 转换到 COFF 期间失败:文件无效或损坏(中)
前言
在 上一篇文章 中,我们总结了使用 windbg
和 IDA
找出 cvtres.exe
报错的根本原因,但是留下了几个细节问题。本篇文章就来把这几个细节问题“格”干净。
几个值得深究的问题
-
为什么链接的时候需要调用 cvtres.exe 呢?
微软官方描述 摘录如下:
You can specify a .res file when linking a program. The .res file is created by the resource compiler (RC). LINK automatically converts .res files to COFF. The CVTRES.exe tool must be in the same directory as LINK.exe or in a directory specified in the PATH environment variable.
stackoverflow 上有一个更加详细的描述,摘录如下:
Input files must have the Common Object File Format (COFF) format. If an input file is not COFF, the linker automatically tries to convert 32-bit OMF objects to COFF, or runs CVTRES.EXE to convert resource files. This message indicates that the linker could not convert the file. This can also occur when using an incompatible version of CVTRES.EXE from another installation of Visual Studio, the Windows Development Kit, or .NET Framework.
-
有没有更好的设置条件断点的方式?目前的语法实在是太难用了。
可以使用
dx
提供的语法来设置条件断点。先定义一个辅助函数@$get_wfs_open_file_param
用来获取file
参数的值。dx @$get_wfs_open_file_param = (esp_value => ((wchar_t*)*((int*)esp_value+1)).ToDisplayString("sub"))
设置条件断点
bp /w "@$get_wfs_open_file_param(@esp).EndsWith(\"510.res\")" MSVCR120!_wfsopen
是不是比传统的方式好理解的多?如果不想定义辅助函数,可以直接像下面这样设置条件断点。
bp /w "((wchar_t*)*((int*)@esp+1)).ToDisplayString(\"sub\").EndsWith(\"510.res\")" MSVCR120!_wfsopen
-
有什么简单的办法可以查看
__piob
数组中元素的内容吗?在前一篇文章中,只是通过逻辑推理确定了当尝试打开
510.res
时,__piob
数组中的每个元素都被占用了。并没有实际查看其内容。其实,可以通过dx
命令非常方便的查看想要查看的内容。比如,可以只查看那些可用的记录项(值为NULL
或者不满足inuse()
和str_locked()
),也可以只查看那些不可用的记录项。为了让代码更容易理解,定义了几个辅助变量及函数。
$$ 以下变量定义在 VC\crt\src\stdio.h 中 dx @$ioReadFlag = 0x1 dx @$ioWriteFlag = 0x2 dx @$ioRWFlag = 0x80 dx @$ioLockedFlag = 0x8000 $$ inuse() 和 str_locked() 定义在 VC\crt\src\file2.h 中 dx -r0 @$IsInUse = (f => f->_flag & (@$ioReadFlag | @$ioWriteFlag | @$ioRWFlag)) dx -r0 @$IsLocked = (f => f->_flag & @$ioLockedFlag) $$ 使用自定义变量 my_piob 指向 __piob dx -r0 @$my_piob = (FILE*[512])MSVCR120!__piob $$ 找到所有可用的记录的数量 dx @$my_piob.Where(f => f == 0 || (@$IsInUse(f) && @$IsLocked(f) ) ).Count() $$ 找到所有不可用的记录的数量 dx @$my_piob.Where(f => !(f == 0 || (@$IsInUse(f) && @$IsLocked(f) ) ) ).Count()
当打开
510.res
中断下来时,可以使用以上脚本找出所有不可用记录,发现没找到任何记录,说明所有记录都被占用了。 -
为什么在打开
510.res
的时候就报错了?应该可以打开 512 个文件才对?猜测应该是已经打开了一些文件。为了验证这个猜测,再次调试。
当
cvtres.exe
中断到windbg
后,执行x MSVCR120!__piob
查看__piob
的值。可以看到此时
__piob
的地址是0x6250fe00
,值是0
。说明此时还没被赋值。通过ba w4 0x6250fe00
设置内存写断点设置好后,执行g
命令恢复运行。几乎立刻中断到windbg
中。可以看到
__piob
会在__initstdio()
函数中被初始化。而且前_IOB_ENTRIES
项(值为20
)的值会被_iob
中对应的值赋值。而_iob
的前三项是被占用的。如下图:所以,
cvtres.exe
最多只能通过_wfsopen()
函数打开512-3 = 509
个文件。也就是说,只能打开1.res ~ 509.res
,打开510.res
的时候会报错。注意:
_iob
的前三项分别指向了stdin
,stdout
,stderror
。我是怎么知道的?看代码呗。
除了通过
_iob
初始化代码的注释可以看出来前三项的意义,也可以通过stdio.h
中的宏定义看出来。
More
还有一个小问题没解决 —— 为什么设置 PATH
环境变量后,直接通过文件名启动程序,windbg
会报错?下一篇文章再“格”吧。
总结
- 命令行程序默认会打开三个文件
stdin、stdout、stderror
。所以,crt
默认能同时打开的文件数是509
个。 dx
是真的香。不仅强悍,而且还易用,符合直觉。
参考资料
https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/setting-a-conditional-breakpoint
https://docs.microsoft.com/en-us/cpp/build/reference/dot-res-files-as-linker-input?view=msvc-170
https://stackoverflow.com/questions/61581826/visual-studio-2019-cvt1101-lnk1123-fatal-error
vs2013
自带的 crt
源码