关闭优化时无法解析的外部符号 __aullshr

2024-03-01

我正在使用 Visual Studio 2015 C/C++ 编译器编译一段 UEFI C 代码。

编译器的目标是IA32,不是 X64。

当使用“/O1”打开优化时,构建正常。

When 关闭使用“/Od”优化,构建给出以下错误:

error LNK2001: unresolved external symbol __aullshr

根据here https://uefi.blogspot.com/2010/02/why-why-do-i-get-unresolved-externals.html,有一个解释为什么这种编译器可以隐式调用函数:

事实证明,这个函数是几个函数之一编译器支持 功能由 Microsoft C/C++ 编译器显式调用。 在这种情况下,每当 32 位编译器运行时都会调用此函数 需要将两个 64 位整数相乘。 EDK 未链接 微软的库并没有提供这个功能。

还有其他类似的功能吗?当然,64 位还有几个 除法、余数和shifting.

但根据here https://en.wikipedia.org/wiki/Intrinsic_function:

...实现的编译器内在函数一般启用它们仅当程序请求优化时...

那么当我显式关闭优化时,如何仍然调用这些函数/Od??

添加 1 - 2:32 PM 2/16/2019

看来我错了__aullshr功能。

它不是编译器内部函数。根据here https://hero.handmade.network/forums/code-discussion/t/94-guide_-_how_to_avoid_c_c++_runtime_on_windows,原来是一个运行时库函数,其实现可以在:C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\crt\src\intel\ullshr.asm or C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\crt\src\i386\ullshr.asm

此类 VC 运行时函数是由编译器引入的,用于 32 位应用程序执行 64 位操作。

但我还是不知道为什么/O1可以构建通行证/Od失败的? 看来优化开关会影响VC运行库的使用。

添加 2 - 下午 4:59 2/17/2019

我找到了导致构建失败的代码。

事实证明是一些C 结构体位域操作。有一个 64 位 C 结构,其中有许多由单个 UINT64 变量支持的位字段。当我注释掉访问这些位字段的单行代码时,构建就通过了。它似乎_aullshr()函数用于访问这些位字段时/Od已指定。

由于这是固件代码的一部分,我想知道关闭优化是否是一个好习惯/Od?

添加 3 - 上午 9:33 2/18/2019

我为 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/拱:IA32/c /GS- /W4 /Gs32768 /D UNICODE/O1b2/GL /EHs-c- /GR- /GF /Gy /Zi /Gm /Gw/Od /Zl test.c

(add:VC++ 2015 编译器给出以下警告test.obj:

警告 C4214:使用了非标准扩展:位字段类型除外 整数

)

然后编译DllMain.obj:

cl /无标志/拱:IA32/c /GS- /W4 /Gs32768 /D UNICODE/O1b2/GL /EHs-c- /GR- /GF /Gy /Zi /Gm /Gw/Od/Zl DllMain.c

然后链接DllMain.obj to the test.obj

链接 DllMain.obj ..\aullshr\test.obj /NOLOGO /NODEFAULTLIB /IGNORE:4001 /OPT:REF /OPT:ICF=10 /MAP /ALIGN:32 /SECTION:.xdata,D /节:.pdata,D/机器:X86/LTCG /SAFESEH: 否 /DLL /ENTRY:DllMethod1 /驱动程序

它会给出以下错误:

生成代码完成生成代码test.obj:错误LNK2001:未解析的外部符号 __aullshrDllMain.dll:致命错误 LNK1120:1 个未解析的外部

  1. 如果我删除位字段操作代码HERE在test.c中,链接错误将会消失。

  2. 如果我只删除/Od从编译选项test.c,链接错误就会消失。

添加 4 - 12:40 PM 2/18/2019

感谢 @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

linkDllMain.obj /NOLOGO /NODEFAULTLIB /IGNORE:4001 /OPT:REF /OPT:ICF=10 /MAP /ALIGN:32 /SECTION:.xdata,D /SECTION:.pdata,D /机器:X86 /LTCG /SAFESEH:NO /DLL /ENTRY:DllMethod1 /DRIVER

此问题可以重现为:

  • 适用于 x86 的 Microsoft (R) C/C++ 优化编译器版本 18.00.40629 (VS2013)

  • 适用于 x86 的 Microsoft (R) C/C++ 优化编译器版本 19.00.24210 (VS2015)

  • 适用于 x86 (VS2015) 的 Microsoft (R) C/C++ 优化编译器版本 19.00.24215.1

按照规定UEFI 编码标准 5.6.3.4 位字段 https://edk2-docs.gitbooks.io/edk-ii-c-coding-standards-specification/content/5_source_files/56_declarations_and_types.html#table-8-efi-constants :

位字段只能是 INT32、有符号 INT32、UINT32 或 typedef 名称定义为三个 INT32 变体之一。

所以我最终的解决方案是修改UEFI代码来使用UINT32代替UINT64.


用于创建 UEFI 应用程序的构建设置忽略了 MSVC 的代码生成期望可用的辅助函数的静态库。 MSVC 的代码生成有时会插入对辅助函数的调用,这与 gcc 在 32 位平台上执行 64x64 乘法或除法或各种其他操作的方式相同。 (例如,没有硬件 popcnt 的目标上的 popcount。)

在这种情况下,将 MSVC 手动引入不太愚蠢的代码生成(本身就是一件好事)恰好可以消除代码库中辅助函数的所有使用。这很好,但不能修复您的构建设置。如果您将来添加需要帮助程序的代码,它可能会再次崩溃. uint64_t shr(uint64_t a, unsigned c) { return a >> c; }即使在编译时也包含对辅助函数的调用-O2.

在没有优化的情况下按常数进行移位使用_aullshr,而不是内联为shrd / shr. 这个确切的问题(坏了-Od构建)会重复出现uint64_t x ; x >> 4或者你的来源中的东西。

(我不知道 MSVC 在哪里保存其辅助函数库。我们认为它是一个静态库,您可以在不引入 DLL 依赖项的情况下链接它(对于 UEFI 来说是不可能的),但我们不知道它是否可能与某些 CRT 启动捆绑在一起您需要避免链接 UEFI 的代码。)


通过这个例子,未优化与优化的问题很明显。带优化的MSVC不需要辅助函数,但它很脑残-Od code does.

对于位域访问,MSVC 显然使用位域成员的基本类型的右移。在您的情况下,您将其设置为 64 位类型,并且 32 位 x86 没有 64 位整数移位(使用 MMX 或 SSE2 除外)。和-Od即使对于常量计数,它也会将数据放入 EDX:EAX,将移位计数放入cl(就像 x86 移位指令一样),并调用__aullshr.

  • __a = ??
  • ull= 无符号长长。
  • shr= 右移(如同名的 x86 asm 指令)。
  • 它需要班次计数cl,与 x86 移位指令完全相同。

来自 Godbolt 编译器资源管理器,x86 MSVC 19.16-Od https://godbolt.org/#z:OYLghAFBqd5QCxAYwPYBMCmBRdBLAF1QCcAaPECAM1QDsCBlZAQwBtMQBGAOgCYAOAJyCALAFYADPwDswucNIArLqVbNaoZAFJeAIR27SAZ1QBXYsg4ByAgE8ADpixUA1KdpG8wWk5es6wH4BLgCqAJIAcgAqAGwiWgDM%2BhIAglqpdo7ObrR4dC5a0skpLqUuRgTEpsgEBUXpJWWZTpiu7p7evv4aQT0ARlQA%2BgSJxWUuAPQTzdntXj7oLgPDow3j48u1VHiYrOicLiAunDGrqetlmy7bu%2Bi8h8enSWsXS0NbO3sJD09jr1c3PYiB6cMRnRoXAGfdBiB68cGvN7Da7QmIghH/d4o27SdHPc6Y5GA9D8PG6UpTRIAYUSABE6QyEvSmYzmWyXpCscTBGSOesobdOBJeQTOUToZwDkdOBixR9BfdpaNJhNqaz1SzNeyWaL%2BVyJd8lfiIXrxYLgUa/qVCrSXLpCEZZTkOgteoFuoFacwCMxwTaXGEGAwQthBgwogAlEJUqJ%2B1J4egNACymAICAwnAgoUisWBry9PpcAEoGoUrQGgyGw5Ho1FERcwkYjKZMAxKtURsbxo3m632zVuAXmAUmS4h3GTXhXBAey221UB/aCEZuMTSazjgVeKdtwGm3P%2BwRuEuV8TOPcN5wi3VyVMRzStRuABLYCPYPnjYip8y0Y5Om18rsRiYHyZYfmUX4ED%2BLgSP%2B0j0qkAGIYhvAJAmyCsKYWD3hU%2BD0NwCCJO%2BqSmAmBBxMiRgIMQECkfQFG1MwpDOvMvjINeZYuJB0HDkRREuNoSR1LSVhFqoIBWGIVikLQEkSNJqASTSegGOUZgWJgW4JJw0kEHJoliQA1iACQJNw/ASAkEgxBIggSNI/AxJwsjiVYIjSQAtlwEgSDJ%2BmkIpVjSUYIC%2BXpVjyWJcCwEgaAefYeDsGQFAQHFCVJSAABuyD2PYgyZZwgiDCcgwAB6OaQ2ysAQmDECFEB9P5fQJswxC2BJOmkHFHmYPQADytCsO1EXSVgHnqMA7D%2BfgX41HgmWYCFI2kJgpWYMgpg1R10lkbs/msHgfTEK1thUhg1idZUeBeVYOliTQ9BMGwHA8AIwjiFIsjyAoagaCgBgGKoh0hbAtDMD1IB9KgqAVKQC3EB1MTSAZAX2AQeQeBJAC0fXoMF6mWFwYmsBJUl%2BctgXlWiHlGNlLgFYI3AnC4EC4IQJBaZwzFnfFiW1Zz17Kfoei6fpRZGSZYh8BIAhiLwghCjEMQJErYiue55PyQFEnBaFpDhZFpDRVAMCICgqC80l5CUGlfMI9luX5YVxUxGVFVVTVdWUI1y3NWDbXbV1Fs9f1g3DVrY0TVNy0zet6MLUtWuretm0XTt9B7ctB1HSdZ1YIHV03XdlV0IwLBTa9QiiJIMjfT9E3/SpehA30INQGDENQzDBBw7ViPI%2BLqPo3QS047w%2BPmITV6uWTskUxJVMuDTdMM0zaKs/gRDEJz3MW%2Bl/M6AkvCCwDIv62LEtWdw71SE5/CcCI0jiGrJNuZ5IBiFL2m8JwKsSCISMRDOREJrBSOtjB6wNqJI2psICxT3nba2qUEEZQwhNaQPlKqJU9vVH2Ws/YnUDt1XqBABpDWmpgcaGho4RzwLNeOi1/LJw2ltW66caqvy1tnY6bU85p31sQa6217qlyehXPgVcPq1zrqoBu2gm6GGzm3CAHcOBd1hvDfuKNUBowxqPPq3wsYeSPhPDSRMZ7STnlrSmjksZxAEr9QI0huASBcSzNmW8d4uB5vvbeh8EgnwUaLEag8ECYGYFgBGEBiYSQ1l5EQIhuAxBkI5BIggxBHzEPwbcoDtZBQgWFC%2BpBjJXxvo5Tg99H7P1cgkSx/lArBMijEqw49ckNPPiEsS8NPB0BACIIAA%3D, with UINT64作为位域成员类型。

;; from int Method1(unsigned __int64) PROC 
    ...
   ; extract IssueStruct.Bits.field8
    mov     eax, DWORD PTR _IssueStruct$[ebp]
    mov     edx, DWORD PTR _IssueStruct$[ebp+4]
    mov     cl, 57                                    ; 00000039H
    call    __aullshr        ; emulation of   shr  edx:eax,  cl
    and     eax, 1
    and     edx, 0
    ;; then store that to memory and cmp/jcc both halves.  Ultra braindead

显然,对于恒定移位和仅访问 1 位,这很容易优化,因此 MSVC 实际上并不调用辅助函数-O2。但它的效率仍然很低!它无法完全优化掉基本类型的 64 位,即使没有一个位域宽于 32。

; x86 MSVC 19.16 -O2   with unsigned long long as the bitfield type
int Method1(unsigned __int64) PROC                              ; Method1, COMDAT
    mov     edx, DWORD PTR _Data$[esp]       ; load the high half of the inputs arg
    xor     eax, eax                         ; zero the low half?!?
    mov     ecx, edx                         ; copy the high half
    and     ecx, 33554432       ; 02000000H  ; isolate bit 57
    or      eax, ecx                         ; set flags from low |= high
    je      SHORT $LN2@Method1
    and     edx, 536870912      ; 20000000H   ; isolate bit 61
    xor     eax, eax                          ; re-materialize low=0 ?!?
    or      eax, edx                          ; set flags from low |= high
    je      SHORT $LN2@Method1
    mov     eax, 1
    ret     0
$LN2@Method1:
    xor     eax, eax
    ret     0
int Method1(unsigned __int64) ENDP                              ; Method1

显然,这是非常愚蠢的实现0对于低半部分而不是仅仅忽略它。如果我们将位域成员类型更改为,MSVC 会做得更好unsigned。 (在 Godbolt 链接中,我将其更改为bf_t所以我可以使用与 UINT64 分开的 typedef,为其他联合成员保留它。)


具有基于的结构unsigned field : 1位域成员,MSVC 不需要帮助程序-Od

它甚至可以在以下位置编写更好的代码-O2,所以你绝对应该在实际的生产代码中这样做。仅使用uint64_t or unsigned long long需要比 32 位更宽的字段成员,如果您关心 MSVC 的性能,它显然对位字段成员的 64 位类型存在错过优化的错误。

;; MSVC -O2 with plain  unsigned  (or uint32_t) bitfield members
int Method1(unsigned __int64) PROC                              ; Method1, COMDAT
    mov     eax, DWORD PTR _Data$[esp]
    test    eax, 33554432                     ; 02000000H
    je      SHORT $LN2@Method1
    test    eax, 536870912                    ; 20000000H
    je      SHORT $LN2@Method1
    mov     eax, 1
    ret     0
$LN2@Method1:
    xor     eax, eax
    ret     0
int Method1(unsigned __int64) ENDP                              ; Method1

我可能会像这样无分支地实现它((high >> 25) & (high >> 29)) & 1 with 2 shr说明和2and说明(以及mov)。不过,如果确实是可预测的,那么分支就是合理的并且打破了数据依赖性。不过,clang 在这里做得很好,使用not + test同时测试这两个位。 (和setcc再次得到整数结果)。这比我的想法有更好的延迟,特别是在没有 mov-elimination 的 CPU 上。 clang 也没有错过对基于 64 位类型的位字段的优化。无论哪种方式,我们都会得到相同的代码。

# clang7.0 -O3 -m32    regardless of bitfield member type
Method1(unsigned long long):                            # @Method1(unsigned long long)
    mov     ecx, dword ptr [esp + 8]
    xor     eax, eax           # prepare for setcc
    not     ecx
    test    ecx, 570425344     # 0x22000000
    sete    al
    ret

UEFI编码标准:

EDK II 编码标准 5.6.3.4 位字段 https://edk2-docs.gitbooks.io/edk-ii-c-coding-standards-specification/content/5_source_files/56_declarations_and_types.html#5634-bit-fields说:

  • 位字段只能是类型INT32, 签INT32, UINT32,或定义为以下三个之一的 typedef 名称INT32变种。

我不知道为什么他们要编出这些“INT32”名称,而 C99 已经非常好了int32_t。目前还不清楚他们为什么要实行这一限制。也许是因为 MSVC 错过优化错误?或者也许通过禁止一些“奇怪的东西”来帮助人类程序员理解。

gcc 和 clang 不会发出警告unsigned long long作为位域类型,即使在 32 位模式下并且-Wall -Wextra -Wpedantic,在 C 或 C++ 模式下。我不认为 ISO C 或 ISO C++ 有问题。

更远,是否应该阻止使用 int 类型的位字段? https://stackoverflow.com/questions/25940635/should-use-of-bit-fields-of-type-int-be-discouraged指出简单的int不鼓励将其作为位域类型,因为符号是实现定义的。 ISO C++ 标准讨论了位域类型char to long long.

我认为你的 MSVC 关于非的警告int位域必须来自某种编码标准执行包,因为 Godbolt 上的普通 MSVC 即使使用 `-Wall 也不会这样做。

警告 C4214:使用非标准扩展:除 int 之外的位字段类型

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

关闭优化时无法解析的外部符号 __aullshr 的相关文章

随机推荐

  • R data.table 在多个条件下进行子集化。

    使用以下数据集 如何编写 data table 调用来对该表进行子集化 并返回该客户的所有客户 ID 和关联订单 如果该客户曾经购买过 SKU 1 预期结果应返回一个表 其中排除该条件下的 cid 3 和 5 以及匹配 sku 1 的客户的
  • STDIN 或文件作为 Hadoop 环境中的映射器输入?

    因为我们需要将一堆文件读入映射器 在非 Hadoop 中 环境 我用的os walk dir and file open path mode 读入 每个文件 然而 在 Hadoop 环境中 当我读到 HadoopStreaming 转换 将
  • AjaxControlToolkit NoBotState 始终为 InvalidBadResponse

    我正在尝试实现 AjaxControlToolkit NoBot 但我总是得到 falseIsValid 方法 状态值始终为无效不良响应 我在这里错过了什么吗 ASCX 代码 buttons textboxes etc
  • Common Lisp 中有停止解释器的命令吗?

    我正在寻找一个表达式 它会导致解释器在求值时退出 我发现了很多特定于实现的内容 但在 HyperSpec 中没有找到 我想知道是否有一些我在规范中没有看到的定义 我发现 quit 被 CLISP 和 SLIME 认可 并且 exit 仅被
  • 双击 UITabBarController Tab 转到导航控制器的根目录

    我有一个带有 2 个 UINavigationController 的 UITabBarController 设置 一个 UINavigationController 有一个 UIViewController 另一个 UINavigatio
  • 返回按钮和刷新之前的活动

    如果我们有两个活动 文件列表和最后修改时间 文件编辑活动 用户从列表中选择一个文件并进入文件编辑活动 完成编辑后 用户按后退按钮返回到文件列表 该列表不会重新加载 因此刚刚编辑的文件修改时间显示的值不正确 按下后退按钮后刷新文件列表的正确方
  • 根据设备是 Android 平板电脑还是手机使用不同的主题

    我想知道如何根据设备是平板电脑还是手机来更改活动的主题 我有一个设置活动 android style Theme Black NoTitleBar以它为主题 在平板电脑上 我希望这个活动的主题是这样的 android style Theme
  • RxSwift:按住按钮时可观察

    如何创建在按住按钮时重复流式传输事件的 Observable 即使我也在寻找你的问题的解决方案 我从 RxSwift 松弛通道获得了帮助 let button submitButton rx controlEvent TouchDown b
  • MySQL PHP 不兼容

    我在本地运行 WAMP 但连接到远程 MySQL 数据库 PHP本地版本是最新的5 3 0 远程数据库之一 版本 5 0 45 工作正常 但是 我尝试连接的另一个远程数据库 版本 5 0 22 在死亡之前抛出以下错误 警告 mysql co
  • 动态模型操纵

    我在谷歌上搜索有关模型操作的最佳实践 显然 在 4 x 中 你有这个函数 setField 例子here http www learnsomethings com 2012 10 05 dynamically changing the mo
  • Mac OS 使用 Java 推送通知

    我想在我的 JavaFX 项目中添加 Mac OS 风格的推送通知 但这实际上非常困难 我正在遵循已接受的答案中描述的方法 在Java中使用10 8通知中心 https stackoverflow com questions 1172065
  • 选择在 Mozilla Firefox 中触发父元素上的 MouseLeave 事件

    我遇到以下问题 在 Mozilla Firefox 中 每当我将鼠标悬停在dropdown inside一个表 它会触发mouseleave尽管鼠标光标仍在表内 但表的事件 在Chrome或Edge中不存在这样的问题 这是我的代码和示例数据
  • Python 有相当于 Perl 的 qq 的东西吗?

    Using qq Perl 允许几乎任何字符用作引号来定义包含以下内容的字符串 and 无需逃避它们 qq She said Don t qq And he said I won t 特别方便 因为我的键盘有 几乎从未使用过 Python
  • ASP.net“{Controller}/”返回 403.14 错误

    我正在开发的一个项目遇到一个奇怪的问题 问题在于指向某个 URL localhost 62168 Images Index 的链接 我有链接到该 URL 的按钮 但是当访问 localhost 62168 Images 时 它会返回 HTT
  • 在node.js(express)中获取MySQL数据并使用EJS打印

    我正在尝试获取 MySQL 表的数据并使用 EJS 以 HTML 格式打印它们 但这不起作用 它告诉我print not defined 我应该怎么办 router get data function req res res render
  • 未为项目设置 OutputPath 属性

    构建我的 Jenkins MSBuild 解决方案给了我这个错误 c WINDOWS Microsoft NET Framework v4 0 30319 Microsoft Common targets 483 9 error The O
  • 添加单选按钮以在 Shiny 中选择 DataTable 行

    I need to add radiButtons to select rows in Data i e the radiobutton choosen should be passed to input I cannot use buil
  • 在单个查询中对外部和内部嵌入数组进行计数

    id ObjectId 5dbdacc28cffef0b94580dbd comments id ObjectId 5dbdacc78cffef0b94580dbf replies id ObjectId 5dbdacd78cffef0b9
  • Pandas DataFrame:滚动函数和扩展函数之间的区别

    任何人都可以帮助我从 pandas 文档中给出的示例中理解滚动函数和扩展函数之间的区别 df DataFrame B 0 1 2 np nan 4 df B 0 0 0 1 1 0 2 2 0 3 NaN 4 4 0 df expandin
  • 关闭优化时无法解析的外部符号 __aullshr

    我正在使用 Visual Studio 2015 C C 编译器编译一段 UEFI C 代码 编译器的目标是IA32 不是 X64 当使用 O1 打开优化时 构建正常 When 关闭使用 Od 优化 构建给出以下错误 error LNK20