Встроенная сборка С++ (компилятор Intel): LEA и MOV ведут себя по-разному в Windows и Linux

Я конвертирую огромную DLL Windows для работы как в Windows, так и в Linux. У dll есть много сборок (и инструкции SS2) для обработки видео.

Теперь код компилируется как на Windows, так и на Linux с использованием компилятора Intel, входящего в состав Intel ComposerXE-2011, на Windows и Intel ComposerXE-2013 SP1 на Linux.

Выполнение, однако, приводит к сбоям в Linux при попытке вызвать указатель на функцию. Я проследил код в gdb, и действительно, указатель функции не указывает на требуемую функцию (тогда как в Windows в делает). Почти все остальное отлично работает.

Это последовательность кода:

...
mov    rdi, this
lea    rdx, [rdi].m_sSomeStruct
...
lea    rax, FUNCTION_NAME                # if replaced by 'mov', works in Linux but crashes in Windows
mov    [rdx].m_pfnFunction, rax
...
call   [rdx].m_pfnFunction               # crash in Linux

где:

1) 'this' имеет член структуры m_sSomeStruct.

2) m_sSomeStruct имеет член m_pfnFunction, который является указателем на функцию.

3) FUNCTION_NAME - свободная функция в одном модуле компиляции.

4) Все эти чистые функции сборки объявлены голыми.

5) 64-разрядная среда.

Что меня больше сбивает с толку, так это то, что если я заменю инструкцию "lea", которая должна загрузить адрес функции в rax с инструкцией "mov", она отлично работает в Linux, но сбой в Windows. Я проследил код как в Visual Studio, так и в gdb и, по-видимому, в Windows "lea" дает правильный адрес функции, тогда как в Linux "mov" делает.

Я попытался найти ссылку на сборку Intel, но не нашел там ничего, что могло бы помочь мне (если я не искал нужное место).

Любая помощь приветствуется. Спасибо!


Изменить Подробнее:

1) Я попытался использовать квадратные скобки

lea    rax, [FUNCTION_NAME]

но это не изменило поведение в Windows и Linux.

2) Я посмотрел на дизассемблер в gdb и Windows, похоже, оба дают те же инструкции, которые я на самом деле написал. Еще хуже то, что я попытался поместить оба lea/mov один за другим, и когда я смотрю на них при разборке в gdb, адрес, напечатанный после инструкции после знака # (который я предполагаю, является адрес, который будет сохранен в регистре) на самом деле тот же, и НЕ является правильным адресом функции.

В gdb disassembler это выглядело как

lea  0xOffset1(%rip), %rax   # 0xSomeAddress
mov  0xOffset2(%rip), %rax   # 0xSomeAddress

где оба (SomeAddress) были идентичны, и оба смещения были отключены с той же разницей между инструкциями lea и mov, Но каким-то образом, когда я проверяю содержимое регистров после каждого выполнения, mov кажется, помещается в правильное значение!!!!

3) Член-переменная m_pfnFunction имеет тип LOAD_FUNCTION, который определяется как

typedef void (*LOAD_FUNCTION)(const void*, void*);

4) Функция FUNCTION_NAME объявлена ​​в .h(в пространстве имен) как

void FUNCTION_NAME(const void* , void*);

и реализован в .cpp как

__declspec(naked) void namespace_name::FUNCTION_NAME(const void* , void*)
{
...
}

5) Я попытался отключить оптимизацию, добавив

#pragma optimize("", off)

но я все еще имею ту же проблему

Ответ 1

С выключенной стороны, я подозреваю, что путь, связанный с DLL, работает в последнем случае, состоит в том, что FUNCTION_NAME - это ячейка памяти, которая фактически будет установлена ​​на загруженный адрес функции. То есть, это ссылка (или указатель) на функцию, а не на точку входа.

Я знаком с Win (а ​​не с другим), и я видел, как вызов функции может быть

(1) генерирует CALL для этого адреса, который заполняется во время соединения. Достаточно нормальный для функций в одном модуле, но если он обнаружил в момент ссылки, что он в другой DLL, тогда Библиотека импорта является заглушкой, которую компоновщик считает такой же, как любая нормальная функция, но не более чем JMP [????]. Таблица адресов для импортированных функций организована так, чтобы иметь байты, которые кодируют JMP-инструкцию непосредственно перед полем, в котором будет сохранен адрес. Таблица заполняется с временем загрузки DLL.

(2) Если компилятор знает, что функция будет в другой DLL, она может генерировать более эффективный код: он кодирует косвенный CALL адресу, указанному в таблице импорта. Функция заглушки, показанная в (1), имеет ассоциированное с ней имя символа, а также поле, содержащее адрес, имеет также имя символа. Оба они названы для функции, но с разными "украшениями". В общем, программа может содержать ссылки на исправления для обоих.

Итак, я догадываюсь, что используемое вами имя символа соответствует функции заглушки на одном компиляторе и (что работает аналогичным образом) соответствует указателю на другой платформе. Возможно, ассемблер присваивает неподходящее имя одному или другому в зависимости от того, объявлен ли он как импортированный, а параметры разные на двух инструментальных цепочках.

Надеюсь, что это поможет. Я предполагаю, что вы можете посмотреть время выполнения в отладчике и посмотреть, помогает ли это выше, чтобы вы интерпретировали адрес и материал вокруг него.

Ответ 2

Прочитав разницу между mov и lea здесь Какова цель инструкции LEA? мне кажется, что в Linux есть еще один дополнительный уровень косвенности, добавленный в указатель функции. Инструкция mov приводит к тому, что дополнительный уровень косвенности передается, а в Windows без этой дополнительной косвенности вы используете lea.

Вы случайно компилируете с PIC в Linux? Я мог видеть, что добавление дополнительного слоя косвенности.