Неразрешенный внешний символ __aullshr при выключенной оптимизации - PullRequest
0 голосов
/ 16 февраля 2019

Я компилирую часть кода C UEFI с помощью компилятора Visual Studio 2015 C / C ++.

Компилятор нацелен на IA32 , а не на X64.

При включении оптимизации с помощью "/ O1", сборка в порядке.

Когда отключив оптимизацию с помощью "/ Od", сборка выдает ниже ошибку:

error LNK2001: unresolved external symbol __aullshr

Согласно здесь , есть объяснение, почему такого родаиз функций может вызываться неявно компилятором:

Оказывается, что эта функция является одной из нескольких функций поддержки компилятора , которые явно вызываются Microsoft C /Компилятор C ++.В этом случае эта функция вызывается всякий раз, когда 32-разрядному компилятору нужно умножить два 64-разрядных целых числа вместе.EDK не связывается с библиотеками Microsoft и не предоставляет эту функцию.

Существуют ли другие функции, подобные этой?Конечно, еще несколько для 64-битного деления, остатка и смещения .

Но согласно здесь :

...Компиляторы, которые реализуют встроенные функции , как правило, включают их , только когда программа запрашивает оптимизацию ...

Так как же такие функции можно вызывать, когда яявно отключил оптимизацию с помощью /Od ??

ADD 1 - 14:32 2/16/2019

Кажется, я ошибаюсь в функции __aullshr.

Это не встроенная функция компилятора. Согласно здесь , это оказывается библиотечная функция времени выполнения, реализация которой может быть найдена в: 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

Такие функции времени выполнения VC вводятся компилятором для 32-разрядных приложений для выполнения 64-разрядных операций.

Но я до сих пор не знаю, почему /O1 может создавать проход, пока/Od не удалось?Кажется, переключение оптимизации может повлиять на использование библиотеки времени выполнения VC.

ADD 2 - 16:59 PM 2/17/2019

Я нашел код, который вызывает сбой сборки.

Оказывается, какая-то операция с битовым полем структуры C .Существует 64-битная структура C, в которой много битовых полей, поддерживаемых одной переменной UINT64.Когда я закомментирую единственную строку кода, которая обращается к этим битовым полям, сборка проходит.Кажется, функция _aullshr() используется для доступа к этим битовым полям, когда указано /Od.

Поскольку это часть кода прошивки, мне интересно, будет ли хорошей практикой отключать оптимизациюс /Od?

ADD 3 - 9:33 AM 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 / arch: 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: используется нестандартное расширение: типы битовых полей, отличные от int

)

Затем скомпилируйте 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 неразрешенная внешняя сторона

  1. Если я удаляю код управления битовыми полями в ЗДЕСЬ в test.c, ошибка связи исчезнет.

  2. Если я только удалю / Od из опций компиляции для теста .c , ошибка ссылки исчезнет.

ADD 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

ссылка DllMain.obj / NOLOGO / NODEFAULTLIB / IGNORE: 4001 / OPT: REF / OPT: ICF = 10 / MAP / ALIGN: 32 /SECTION:.xdata,D /SECTION:.pdata,D / МАШИНА: X86 / LTCG / SAFESEH: НЕТ/ DLL / ENTRY: DllMethod1 / DRIVER

Эта проблема может быть воспроизведена для:

  • Microsoft (R) C / C ++ Оптимизирующая версия компилятора 18.00.40629 дляx86 (VS2013)

  • Microsoft (R) C / C ++ Оптимизирующая версия компилятора 19.00.24210 для x86 (VS2015)

  • Microsoft (R)Оптимизирующий компилятор C / C ++ версии 19.00.24215.1 для x86 (VS2015)

Как указано в стандарте кодирования UEFI 5.6.3.4 Битовые поля :

Битовые поля могут иметь тип INT32, INT32 со знаком, UINT32 или typedef name определен как один из трех вариантов INT32.

Таким образом, мое окончательное решение - изменить код UEFI для использования UINT32 вместо UINT64.

Ответы [ 2 ]

0 голосов
/ 18 февраля 2019

В настройках сборки для создания приложений UEFI отсутствует статическая библиотека вспомогательных функций, которые, как ожидается, MSVC code-gen будет доступна.Code-gen MSVC иногда вставляет вызовы вспомогательных функций, так же, как gcc делает для 64x64 умножения или деления на 32-битных платформах, или для различных других вещей.(например, popcount для целей без аппаратного popcnt.)

В этом случае удержание 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 делает .

Для доступа к битовому полю MSVC, очевидно, использует правый сдвиг базового типачлен битового поля .В вашем случае вы сделали это 64-битным типом, а 32-битный x86 не имеет 64-битного целочисленного сдвига (за исключением использования MMX или SSE2).С -Od даже при постоянных счетах он помещает данные в EDX: EAX, счетчик сдвигов в cl (как для инструкций по сдвигу в x86) и вызывает __aullshr.

  • __a = ??
  • ull = длинная строка без знака.
  • shr = сдвиг вправо (аналогично инструкции asm для x86 с тем же именем).
  • требуетсячисло сдвигов в cl, точно так же, как инструкции по сдвигу в x86.

Из проводника компилятора Godbolt, x86 MSVC 19.16 -Od, с UINT64 в качестве члена битового поляtype.

;; 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, чтобы я мог использовать typedef отдельно от UINT64, оставив его для другого члена объединения.)


Со структурой, основанной на unsigned field : 1 членов битового поля, MSVC не нужен помощник на -Od

И он даже делает лучший код на -O2, , так что вы обязательно должны сделать это в своемреальный производственный код. Используйте элементы uint64_t или 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 с 2 shr инструкциями и 2 and инструкциями (и mov).Однако, если это действительно предсказуемо, ветвление является разумным и нарушает зависимость данных.clang делает хорошую работу здесь, используя not + test для проверки обоих битов одновременно.(И setcc, чтобы снова получить результат как целое число).Это имеет большую задержку, чем моя идея, особенно на процессорах без MOV-устранения.У 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 Битовые поля говорит, что:

  • Битовые поля могут быть только типа INT32, подписанный INT32, UINT32 или имя определения типа, определенное как один из трех INT32 вариантов.

Я не знаю, почему они составляют эти имена "INT32", когдаУ С99 уже отлично получается int32_t.Также неясно, почему они установили это ограничение.Возможно, из-за ошибки пропущенной оптимизации MSVC?Или, возможно, чтобы помочь пониманию программиста-человека, запретив некоторые «странные вещи».

gcc и clang не предупреждают о unsigned long long как типе битового поля, даже в 32-битном режиме и с -Wall -Wextra -Wpedantic вРежим C или C ++.Я не думаю, что у ISO C или ISO C ++ есть проблема с этим.

Далее, Следует ли препятствовать использованию битовых полей типа int? указывает, что обычный int какТип битового поля не должен поощряться, потому что подпись определяется реализацией.И что в стандарте ISO C ++ обсуждаются типы битовых полей от char до long long.

Я думаю, что ваше предупреждение MSVC о битовых полях, отличных от int, должно быть из какого-то пакета обеспечения соблюдения стандартов кодирования, потому чтообычный MSVC на Godbolt этого не делает даже с `-Wall.

предупреждение C4214: используется нестандартное расширение: типы битовых полей, отличные от int

0 голосов
/ 17 февраля 2019

То, что вы описываете, похоже на одно из следующих:

  • ошибка компилятора, вызванная только с /Od.Было бы очень полезно, если бы вы могли извлечь определения структуры и код нарушения в минимальной программе, которая представляет проблему для экспертов, чтобы исследовать проблему.

  • проблема установки компилятора: вы можетессылки на библиотеку C, несовместимую с вашим компилятором C.Это может вызвать дальнейшие проблемы в других областях вашей программы.Я настоятельно рекомендую переустановить компилятор с нуля.

...