【发布时间】:2019-07-10 05:14:28
【问题描述】:
我正在使用 Visual Studio 2015 C/C++ 编译器编译一段 UEFI C 代码。
编译器的目标是 IA32,而不是 X64。
当用“/O1”开启优化时,构建是OK的。
当关闭使用“/Od”优化时,构建会出现以下错误:
error LNK2001: unresolved external symbol __aullshr
根据here,有一个解释为什么编译器可以隐式调用这种函数:
事实证明,这个函数是几个编译器支持之一 由 Microsoft C/C++ 编译器显式调用的函数。 在这种情况下,只要 32 位编译器调用此函数 需要将两个 64 位整数相乘。 EDK 未链接 与微软的库,不提供此功能。
还有其他类似的功能吗?当然,还有几个 64 位 除法、余数和移位。
但是根据here:
...实现内在函数的编译器通常会启用它们 仅当程序请求优化时...
那么,当我用/Od 明确关闭优化时,这些函数怎么还能被调用??
添加 1 - 2019 年 2 月 16 日下午 2:32
看来我对 __aullshr 函数的看法是错误的。
它不是编译器内部函数。根据here,它原来是一个运行时库函数,其实现可以在:C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\crt\src\intel\ullshr.asm或C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\crt\src\i386\ullshr.asm找到p>
此类 VC 运行时函数由编译器引入,用于 32 位应用程序执行 64 位操作。
但我仍然不知道为什么/O1 可以构建通过而/Od 失败?
看来优化开关会影响VC运行库的使用。
添加 2 - 2019 年 2 月 17 日下午 4:59
我找到了导致构建失败的代码。
原来是一些C 结构位域操作。有一个 64 位 C 结构,它有很多由单个 UINT64 变量支持的位字段。当我注释掉访问这些位字段的单行代码时,构建就通过了。当指定/Od 时,似乎_aullshr() 函数用于访问这些位字段。
由于这是固件代码的一部分,我想知道使用/Od 关闭优化是否是一个好习惯?
添加 3 - 2019 年 2 月 18 日上午 9:33
我为 VS2015 创建了以下最小可重现示例。
首先,有一个静态库项目:
(test.c)
typedef unsigned __int64 UINT64;
typedef union {
struct {
UINT64 field1 : 16;
UINT64 field2 : 16;
UINT64 field3 : 6;
UINT64 field4 : 15;
UINT64 field5 : 2;
UINT64 field6 : 1;
UINT64 field7 : 1;
UINT64 field8 : 1; //<=========
UINT64 field9 : 1;
UINT64 field10 : 1;
UINT64 field11 : 1;
UINT64 field12 : 1; //<=========
UINT64 field13 : 1;
UINT64 field14 : 1;
} Bits;
UINT64 Data;
} ISSUE_STRUCT;
int
Method1
(
UINT64 Data
)
{
ISSUE_STRUCT IssueStruct;
IssueStruct.Data = Data;
if (IssueStruct.Bits.field8 == 1 && IssueStruct.Bits.field12 == 1) { // <==== HERE
return 1;
}
else
{
return 0;
}
}
然后是一个 Windows DLL 项目:
(DllMain.c)
#include <Windows.h>
typedef unsigned __int64 UINT64;
int
Method1
(
UINT64 Data
);
int __stdcall DllMethod1
(
HINSTANCE hinstDLL,
DWORD fdwReason,
LPVOID lpReserved
)
{
if (Method1(1234)) //<===== Use the Method1 from the test.obj
{
return 1;
}
return 2;
}
构建过程:
首先,编译test.obj:
cl.exe /nologo /arch:IA32 /c /GS- /W4 /Gs32768 /D UNICODE /O1b2 /GL /EHs-c- /GR- /GF /Gy /Zi /Gm /Gw /Od /Zl test.c
(添加:VC++ 2015 编译器对test.obj 给出以下警告:
警告 C4214:使用了非标准扩展:位字段类型不是 诠释
)
然后编译DllMain.obj:
cl /nologo /arch:IA32 /c /GS- /W4 /Gs32768 /D UNICODE /O1b2 /GL /EHs-c- /GR- /GF /Gy /Zi /Gm /Gw /Od /Zl DllMain.c
然后将 DllMain.obj 链接到 test.obj
链接 DllMain.obj ..\aullshr\test.obj /NOLOGO /NODEFAULTLIB /IGNORE:4001 /OPT:REF /OPT:ICF=10 /MAP /ALIGN:32 /SECTION:.xdata,D /SECTION:.pdata,D /MACHINE:X86 /LTCG /SAFESEH:NO /DLL /ENTRY:DllMethod1 /DRIVER
会报如下错误:
生成代码完成生成代码test.obj:错误LNK2001: 无法解析的外部符号 __aullshr DllMain.dll:致命错误 LNK1120: 1 个未解决的外部问题
如果我在test.c中删除HERE处的位域操作代码,链接错误就会消失。
如果我只从 test.c 的编译选项中删除 /Od,链接错误将消失。
添加 4 - 2019 年 2 月 18 日下午 12:40
感谢@PeterCordes 在他的评论中,有一种更简单的方法可以重现此问题。只需调用以下方法:
uint64_t shr(uint64_t a, unsigned c) { return a >> c; }
然后使用以下命令编译源代码:
cl /nologo /arch:IA32 /c /GS- /W4 /Gs32768 /D UNICODE /O1b2 /GL /EHs-c- /GR- /GF /Gy /Zi /Gm /Gw /Od /Zl DllMain.c
链接 DllMain.obj /NOLOGO /NODEFAULTLIB /IGNORE:4001 /OPT:REF /OPT:ICF=10 /MAP /ALIGN:32 /SECTION:.xdata,D /SECTION:.pdata,D /MACHINE:X86 /LTCG /SAFESEH:NO /DLL /ENTRY:DllMethod1 /DRIVER
这个问题可以重现:
Microsoft (R) C/C++ 优化编译器版本 18.00.40629 for x86 (VS2013)
Microsoft (R) C/C++ 优化编译器版本 19.00.24210 for x86 (VS2015)
Microsoft (R) C/C++ 优化编译器版本 19.00.24215.1 for x86 (VS2015)
根据UEFI coding standard 5.6.3.4 Bit Fields 中的规定:
位域只能是 INT32、有符号 INT32、UINT32 或 typedef 名称定义为三个 INT32 变体之一。
所以我的最终解决方案是修改 UEFI 代码以使用 UINT32 而不是 UINT64。
【问题讨论】:
-
没什么神秘的,/O1 删除了不必要的代码。谷歌“visual c++ 如何生成汇编列表”以取得成功。
-
或者也许通过优化编译器意识到
a * (uint64_t)b实际上是一个 32x32 => 64 位乘法并使用 1 操作数mul r32在 EDX:EAX 中生成 64 位结果。但是在禁用优化的情况下,它实际上可能对两个输入进行零扩展并调用 64x64 => 64 位乘法函数。 godbolt.org/z/si5064 不,没关系,即使在-Od模式下,MSVC 也没有那么糟糕。 -
您似乎遇到了编译器错误:您能否在使用
/Od编译时出现问题的最小程序中发布结构定义和违规代码? -
@chqrlie 让我检查一下这是否是专有代码并稍后发布。
-
@chqrlie 我添加了一个最小的可重现示例。
标签: c visual-c++ intrinsics bit-fields uefi