Как этот ассемблерный код x86 получает и сохраняет данные, возвращаемые подпрограммой?

У меня есть код на С ++, который вызывает адрес экспортированной функции из другой dll и возвращает структуру. Код на C++ выглядит следующим образом (я изменил имена переменных на A, B, C ...):

// .h 
struct _A { unsigned char _B[32]; } A;

// .cpp
typedef struct _A* (__cdecl *_C)();
_C C = NULL;
...
C = (_C)GetProcAddress(..., ...);
A = *C();

Приведенный выше код (только часть A = *C();) при компиляции в VS 2015 превращается в следующий листинг кода:

A DB 020H DUP (?)
...
call DWORD PTR C
mov  esi, eax
mov  esx, 8
mov  edi, OFFSET A

Как (и где) указатель передается из подпрограммы, хранящейся всего в трех строках mov? Я не могу понять, как данные, переданные из подпрограммы, сохраняются для последующих обращений.

Просто из любопытства я попытался изменить struct _A на _B [11] вместо _B [32], и код изменился на следующий:

call DWORD PTR C
mov  ecx, DWORD PTR [eax]
mov  DWORD PTR A, ecx
mov  edx, DWORD PTR [eax+4]
mov  DWORD PTR A+4, edx
...

Так что для меня это имеет смысл. Это чтение из eax и копирование в A, как и ожидалось. Но как первый ассемблерный код получает и сохраняет структуру, возвращенную подпрограммой?

Возвращаемое значение передается в ax.

Jesper Juhl 26.07.2018 19:54

@JesperJuhl Я знаю, что он передается через eax, но как это сохраняется для последующих ссылок? Например, позже в коде, когда на самом деле упоминается этот A, все, что он, кажется, делает, это нажимает OFFSET A, и затем он может волшебным образом ссылаться на структуру, которую я получил обратно от C (), но как (и когда) было это скопировано? Я не могу понять эту часть, так как все, что я вижу, это три мова, которые заполняют esi, esx и edi и больше ничего не делают ...

Chaewon Lee 26.07.2018 19:59
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
2
83
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

But how is the first assembly code retrieving and storing the struct returned from the subroutine?

Прежде всего, он не возвращает структуру, он возвращает структуру указатель на в EAX. Тип возвращаемого значения функции - struct _A*. Вы не показываете какие, на который он указывает; возможно, какой-то статический буфер в функции, не обеспечивающей потокобезопасность?

Похоже, вы пропустили rep movsd в первом примере после настройки esi, edi и ecx (ваш esx, очевидно, является опечаткой). Это приведет к запоминанию 4*8 = 32 байта из указателя, возвращенного в EAX, в статическое хранилище для A. (Обратите внимание на mov edi, offset A, чтобы получить фактический адрес A в EDI.)

С меньшей структурой он копирует ее с помощью нескольких инструкций mov вместо настройки для rep movsd (что имеет значительные накладные расходы на запуск и является плохим выбором для 32-байтовой копии, если SSE был доступен). т.е. он полностью разворачивает цикл копирования.


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

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

значение самого A составляет 32 байта и не помещается в регистр. Часто в asm вам нужен указатель на объект. push OFFSET A, вероятно, является частью вызова функции, которая принимает A по ссылке (вероятно, явно в исходном коде C++; я не думаю, что какие-либо стандартные соглашения о вызовах x86 реализуют передачу по значению как передачу по константной ссылке, только по неконстантной ссылке, например. для Windows x64 и, возможно, других) .


Ваш компилятор, вероятно, не смог оптимизировать A = foo(); (возвращая большую структуру по значению), передав адрес A непосредственно в качестве выходного указателя.

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

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

Chaewon Lee 26.07.2018 20:40

Питер, его функция возвращает указатель, а не структуру, поэтому она не может использовать скрытый параметр, который используется для возврата структуры.

prl 27.07.2018 03:57

@prl: о, лол, да, это не скрыто, это источник C++ разыменовывает указатель для копирования в вызывающей стороне. / facepalm.

Peter Cordes 27.07.2018 04:50

Другие вопросы по теме