Semenalidery.com

IT Новости из мира ПК
20 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

Frame pointer что это

Стековый фрейм

На примере программы, представленной в предыдущем разделе, разберемся, как используется пространство стека. Во время выполнения подпрограммы применяе­мую ею информацию содержат шесть верхних элементов стека. Эта область состав­ляет собственное рабочее пространство подпрограммы, создаваемое при ее вызове и освобождаемое, когда управление возвращается вызывающей программе. Она называется стековым фреймом. Если для локальных переменных подпрограммы требуется дополнительное пространство, его также можно выделить в стеке.

Пример типичного расположения информации в стековом фрейме приведен на рис. 17.5.

Рис. 16.5. Пример стекового фрейма

В дополнение к указателю стека SP можно использовать еще один регистр, называемый указателем фрейма (Frame Pointer, FP). Он облегчает дос­туп к параметрам подпрограммы и ее локальным переменным. Эти локальные пе­ременные используются только внутри подпрограммы, так что оперативную память для их хранения удобнее всего выделить прямо в стековом фрейме, связанном с данной подпрограммой. В нашем случае предполагается, что подпрограмма получает четыре параметра, применяет три локальные переменные и сохраняет содержи­мое регистров R0 и R1, которые она использует для своих нужд.

Поскольку регистр FP указывает на область оперативной памяти, расположенную непо­средственно над сохраненным в стеке адресом возврата, с его помощью легко обращаться к параметрам и локальным переменным, применяя индексный режим адресации. Параметры адресуются так: 8(FP), 12(FP) и т. д., а локальные пере­менные так: -4(FP), -8(FP). Содержимое регистра FP в ходе выполнения под­программы остается неизменным, тогда как указатель стека SP должен всегда указывать на верхний элемент стека.

Теперь посмотрим, что происходит с указателями SP и FP при созда­нии, использовании и уничтожении стекового фрейма в результате вызова кон­кретной подпрограммы. Будем считать, что перед вызовом подпрограммы SP указывает на старую вершину стека.

Вызывающая программа помещает в стек четыре параметра. Затем выполняет­ся команда CALL, которая помещает в стек адрес возврата и передает управление подпрограмме. Теперь SP указывает на элемент стека, содержащий адрес возврата, и в следующий момент будет выполнена первая команда подпрограммы. Именно в это время указателю фрейма FP присваивается нужный адрес. Поскольку в ка­честве регистра FP обычно выступает один их регистров общего назначения, он может со­держать информацию, нужную вызывающей программе. Поэтому его содержи­мое тоже сохраняется в стеке. А поскольку на вершину стека, которая будет началом стекового фрейма, указывает регистр SP, содержимое данного регистра копиру­ется в регистр FP.

Итак, первые две команды подпрограммы должны быть такими:

После их выполнения регистры SP и FP указывают на сохраненное содержимое регист­ра FP. Далее в стеке выделяется пространство для трех локальных переменных, для чего выполняется команда

Напоследок в стеке сохраняется содержимое регистров R0 и R1. Теперь стековый фрейм сформирован и имеет такую структуру, как на рис. 16.5.

Далее подпрограмма, выполнив свою непосредственную задачу, выталкивает ранее сохраненные значения регистров R0 и R1 обратно в эти же регистры, удаля­ет из стекового фрейма локальные переменные с помощью команды

и выталкивает старое значение регистра FP обратно в этот регистр. Теперь регистр SP указывает на адрес возврата и можно выполнить команду RETURN, возвращающую управление вызывающей программе.

За удаление параметров из стекового фрейма отвечает вызывающая программа. Некоторые из таких параметров могут содержать возвращенные подпрограммой результаты вычислений. После того как это будет сделано, указатель стека должен указывать на исходную вершину стека.

Дата добавления: 2015-09-29 ; просмотров: 1615 ; ЗАКАЗАТЬ НАПИСАНИЕ РАБОТЫ

Попытка понять параметр gcc-fomit-frame-pointer

Я попросил Google дать мне значение gcc опции -fomit-frame-pointer , который перенаправляет меня на ниже заявление.

— fomit-frame-pointer

Не держите указатель кадра в регистре для функций, которые в нем не нуждаются. Это позволяет избежать инструкций по сохранению, настройке и восстановлению указателей кадров; это также делает дополнительный регистр доступным во многих функциях. Это также делает невозможной отладку на некоторых машины.

согласно моим знаниям каждой функции, запись активации будет создана в стеке памяти процесса, чтобы сохранить все локальные переменные и некоторую дополнительную информацию. Я надеюсь, что этот указатель фрейма означает адрес записи активации функции.

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

2 ответов

большинство меньших функций не нуждаются в указателе кадра — большие функции могут нуждаться в нем.

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

Я не думаю, что вы должны «пытаться сделать функции без указателя кадра» как часть вашей стратегии кодирования — как я уже сказал, простые функции не нуждаются в них, поэтому используйте -fomit-frame-pointer , и вы получите еще один регистр, доступный для распределителя регистров, и сохраните 1-3 инструкции по входу/выходу в функции. Если вашей функции нужен указатель фрейма, это потому, что компилятор решает это лучший вариант, чем не использовать указатель кадра. Это не цель иметь функции без указателя кадра, это цель иметь код, который работает правильно и быстро.

обратите внимание, что» отсутствие указателя кадра » должно дать лучшую производительность, но это не какая — то волшебная пуля, которая дает огромные улучшения-особенно не на x86-64, у которого уже есть 16 регистров для начала. На 32-битном x86, так как он имеет только 8 регистров, один из которых является указателем стека и занимает другой, как указатель кадра, означает, что занято 25% пространства регистра. Чтобы изменить это на 12,5%, это довольно улучшение. Конечно, компиляция для 64-бит тоже очень поможет.

это все о регистре BP/EBP/RBP на платформах Intel. Этот регистр по умолчанию имеет сегмент стека (для доступа к сегменту стека не требуется специальный префикс).

EBP-лучший выбор регистра для доступа к структурам данных, переменным и динамически выделенному рабочему пространству в стеке. EBP часто используется для доступа к элементам в стеке относительно фиксированной точки в стеке, а не относительно текущего TOS. Это, как правило, определяет базовый адрес текущего кадра стека, установленного для текущей процедуры. Когда EBP используется в качестве Базового регистра при расчете смещения, смещение вычисляется автоматически в текущем сегменте стека (т. е. сегменте, выбранном в настоящее время SS). Поскольку SS не обязательно указывать явно, кодирование команд в таких случаях более эффективно. EBP также может использоваться для индексирования в сегменты, адресуемые через другие регистры сегментов.

поскольку на большинстве 32-разрядных платформ сегмент данных и сегмент стека одинаковы, эта связь EBP/RBP со стеком больше не является проблемой. Так и на 64-битных платформах: архитектура x86-64, представленная AMD в 2003 году, в значительной степени снизила поддержку сегментации в 64-битном режиме: четыре регистра сегмента: CS, SS, DS и ES вынуждены равняться 0. Эти обстоятельства x86 32-разрядные и 64-разрядные платформы по существу означают, что регистр EBP/RBP может использоваться без префикса в инструкциях процессора, которые обращаются к памяти.

таким образом, опция компилятора, о которой вы писали, позволяет использовать BP/EBP/RBP для других средств, например, для хранения локальной переменной.

под «это позволяет избежать инструкций по сохранению, настройке и восстановлению указателей кадров» подразумевается, избегая следующего кода при вводе каждой функции:

или enter инструкция, которая был очень полезен на процессорах Intel 80286 и 80386.

кроме того, перед возвратом функции используется следующий код:

или leave инструкция.

инструменты отладки могут сканировать данные стека и использовать эти данные Регистра ebp при поиске call sites , т. е. для отображения имен функции и аргументов в порядке их иерархического вызова.

программисты могут возникнуть вопросы о кадрах стека не в широкий термин (что это один объект в стеке, который обслуживает только один вызов функции и сохраняет обратный адрес, Аргументы и локальные переменные), но в узком смысле – когда термин stack frames упоминается в контексте параметров компилятора. С точки зрения компилятора, фрейм стека-это просто код входа и выхода для режима, который подталкивает якорь к стеку-который также может использоваться для отладки и обработки исключений. Инструменты отладки могут сканировать данные стека и используйте эти якоря для обратной трассировки при поиске call sites в стеке, т. е. для отображения имен функции в том порядке, в котором они были вызваны иерархически.

вот почему очень важно понять программисту, что такое стек, с точки зрения компилятора – потому что компилятор может контролировать, Создавать ли этот код или нет.

в некоторых случаях фрейм стека (код входа и выхода для подпрограммы) может быть опущен компилятором, а переменные будут напрямую доступны через указатель стека (SP/ESP/RSP), а не удобный базовый указатель (BP/ESP / RSP). Условия, при которых компилятор опускает фреймы стека для некоторых функций, могут быть разными, например: (1) функция является листовой функцией (т. е. конечной сущностью, которая не вызывает другие функции); (2) не используются исключения; (3) никакие подпрограммы не вызываются с исходящими параметрами в стеке; (4) функция не имеет параметров.

Читать еще:  Sharepoint service desk

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

возвращаясь от обобщений к частностям: если вы будете использовать -fomit-frame-pointer опция компилятора GCC, вы можете выиграть как при вводе, так и при выходе кода для подпрограммы и при наличии дополнительного регистра (если он уже не включен по умолчанию либо сам, либо неявно другими параметрами, в этом случае вы уже получаете выгоду от использования регистра EBP/RBP, и никакой дополнительный выигрыш не будет получен явно указание этой опции, если она уже включена неявно). Обратите внимание, однако, что в 16-битном и 32-битном режимах регистр BP не имеет возможности доступа к 8-битным частям, таким как AX (AL и AH).

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

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

я не нашел никакой официальной информации на gcc.gnu.org о том, как влияют другие параметры -fomit-frame-pointer на платформах x86, этот https://gcc.gnu.org/onlinedocs/gcc-3.4.4/gcc/Optimize-Options.html только заявляет следующее:

— O также включает-fomit-frame-указатель на машинах, где это не мешает отладке.

так что не понятно из документации как таковой ли -fomit-frame-pointer будет включен, если вы просто скомпилируете с помощью одного -O опция на платформе x86. Это может быть проверено эмпирически, но в этом случае нет обязательства от разработчиков GCC не изменять поведение этой опции в будущем без предварительного уведомления.

однако, Питер Кордес указал в комментарии, что есть разница для настроек по умолчанию -fomit-frame-pointer между х86-16 платформ и x86-32/64 платформ.

этот вариант — -fomit-frame-pointer — тоже относится к компилятору Intel C++ 15.0, не только для GCC:

для компилятора Intel, это опция имеет псевдоним /Oy .

вот что написала об этом Intel:

эти параметры определяют, используется ли EBP в качестве регистра общего назначения в оптимизациях. Опции-fomit-frame-pointer и / Oy позволяют использовать это. Опции-fno-опустить-кадр-указатель и / Oy-запретить его.

некоторые отладчики ожидают, что EBP будет использоваться в качестве указателя кадра стека, и не могут создать обратную трассировку стека, если это не так. — Fno-опустить-кадр-указатель и / Oy- параметры направляют компилятор на создание кода, который поддерживает и использует EBP в качестве указателя фрейма стека для всех функций, так что отладчик все еще может создавать обратную трассировку стека без выполнения следующих действий:

For-fno-omit-frame-pointer: отключение оптимизации с помощью-O0 Для /Oy-: отключение /O1, /O2, или / O3 оптимизации Параметр-fno-omit-frame-pointer устанавливается при указании параметра-O0 или-g. Параметр-fomit-frame-pointer устанавливается при указании параметра — O1, — O2, или-O3.

параметр /Oy устанавливается при указании параметра /O1, /O2 или /O3. Option /Oy — устанавливается при указании параметра / Od.

использование параметра-fno-omit-frame-pointer или /Oy-уменьшает количество доступных регистров общего назначения на 1 и может привести к несколько менее эффективному коду.

примечание для систем Linux*: в настоящее время существует проблема с обработкой исключений GCC 3.2. Поэтому компилятор Intel игнорирует это опция, когда GCC 3.2 установлен для C++ и обработка исключений включена (по умолчанию).

имейте в виду, что приведенная выше цитата относится только к компилятору Intel C++ 15, а не к GCC.

Stack frame layout on x86-64

A few months ago I’ve written an article named Where the top of the stack is on x86, which aimed to clear some misunderstandings regarding stack usage on the x86 architecture. The article concluded with a useful diagram presenting the stack frame layout of a typical function call.

In this article I will examine the stack frame layout of the newer 64-bit version of the x86 architecture, x64 [1]. The focus will be on Linux and other OSes following the official System V AMD64 ABI. Windows uses a somewhat different ABI, and I will mention it briefly in the end.

I have no intention of detailing the complete x64 calling convention here. For that, you will literally have to read the whole AMD64 ABI.

Registers galore

x86 has just 8 general-purpose registers available (eax, ebx, ecx, edx, ebp, esp, esi, edi). x64 extended them to 64 bits (prefix «r» instead of «e») and added another 8 (r8, r9, r10, r11, r12, r13, r14, r15). Since some of x86’s registers have special implicit meanings and aren’t really used as general-purpose (most notably ebp and esp), the effective increase is even larger than it seems.

There’s a reason I’m mentioning this in an article focused on stack frames. The relatively large amount of available registers influenced some important design decisions for the ABI, such as passing many arguments in registers, thus rendering the stack less useful than before [2].

Argument passing

I’m going to simplify the discussion here on purpose and focus on integer/pointer arguments [3]. According to the ABI, the first 6 integer or pointer arguments to a function are passed in registers. The first is placed in rdi, the second in rsi, the third in rdx, and then rcx, r8 and r9. Only the 7th argument and onwards are passed on the stack.

The stack frame

With the above in mind, let’s see how the stack frame for this C function looks:

This is the stack frame:

So the first 6 arguments are passed via registers. But other than that, this doesn’t look very different from what happens on x86 [4], except this strange «red zone». What is that all about?

The red zone

First I’ll quote the formal definition from the AMD64 ABI:

The 128-byte area beyond the location pointed to by %rsp is considered to be reserved and shall not be modified by signal or interrupt handlers. Therefore, functions may use this area for temporary data that is not needed across function calls. In particular, leaf functions may use this area for their entire stack frame, rather than adjusting the stack pointer in the prologue and epilogue. This area is known as the red zone.

Put simply, the red zone is an optimization. Code can assume that the 128 bytes below rsp will not be asynchronously clobbered by signals or interrupt handlers, and thus can use it for scratch data, without explicitly moving the stack pointer. The last sentence is where the optimization lays — decrementing rsp and restoring it are two instructions that can be saved when using the red zone for data.

However, keep in mind that the red zone will be clobbered by function calls, so it’s usually most useful in leaf functions (functions that call no other functions).

Recall how myfunc in the code sample above calls another function named utilfunc. This was done on purpose, to make myfunc non-leaf and thus prevent the compiler from applying the red zone optimization. Looking at the code of utilfunc:

This is indeed a leaf function. Let’s see how its stack frame looks when compiled with gcc:

Since utilfunc only has 3 arguments, calling it requires no stack usage since all the arguments fit into registers. In addition, since it’s a leaf function, gcc chooses to use the red zone for all its local variables. Thus, rsp needs not be decremented (and later restored) to allocate space for this data.

Preserving the base pointer

The base pointer rbp (and its predecessor ebp on x86), being a stable «anchor» to the beginning of the stack frame throughout the execution of a function, is very convenient for manual assembly coding and for debugging [5]. However, some time ago it was noticed that compiler-generated code doesn’t really need it (the compiler can easily keep track of offsets from rsp), and the DWARF debugging format provides means (CFI) to access stack frames without the base pointer.

Читать еще:  Sharepoint работа с документами

This is why some compilers started omitting the base pointer for aggressive optimizations, thus shortening the function prologue and epilogue, and providing an additional register for general-purpose use (which, recall, is quite useful on x86 with its limited set of GPRs).

gcc keeps the base pointer by default on x86, but allows the optimization with the -fomit-frame-pointer compilation flag. How recommended it is to use this flag is a debated issue — you may do some googling if this interests you.

Anyhow, one other «novelty» the AMD64 ABI introduced is making the base pointer explicitly optional, stating:

The conventional use of %rbp as a frame pointer for the stack frame may be avoided by using %rsp (the stack pointer) to index into the stack frame. This technique saves two instructions in the prologue and epilogue and makes one additional general-purpose register (%rbp) available.

gcc adheres to this recommendation and by default omits the frame pointer on x64, when compiling with optimizations. It gives an option to preserve it by providing the -fno-omit-frame-pointer flag. For clarity’s sake, the stack frames showed above were produced without omitting the frame pointer.

The Windows x64 ABI

Windows on x64 implements an ABI of its own, which is somewhat different from the AMD64 ABI. I will only discuss the Windows x64 ABI briefly, mentioning how its stack frame layout differs from AMD64. These are the main differences:

  1. Only 4 integer/pointer arguments are passed in registers (rcx, rdx, r8, r9).
  2. There is no concept of «red zone» whatsoever. In fact, the ABI explicitly states that the area beyond rsp is considered volatile and unsafe to use. The OS, debuggers or interrupt handlers may overwrite this area.
  3. Instead, a «register parameter area» [6] is provided by the caller in each stack frame. When a function is called, the last thing allocated on the stack before the return address is space for at least 4 registers (8 bytes each). This area is available for the callee’s use without explicitly allocating it. It’s useful for variable argument functions as well as for debugging (providing known locations for parameters, while registers may be reused for other purposes). Although the area was originally conceived for spilling the 4 arguments passed in registers, these days the compiler uses it for other optimization purposes as well (for example, if the function needs less than 32 bytes of stack space for its local variables, this area may be used without touching rsp).

Another important change that was made in the Windows x64 ABI is the cleanup of calling conventions. No more cdecl/stdcall/fastcall/thiscall/register/safecall madness — just a single «x64 calling convention». Cheers to that!

For more information on this and other aspects of the Windows x64 ABI, here are some good links:

  • Official MSDN page on x64 software conventions — well organized information, IMHO easier to follow and understand than the AMD64 ABI document.
  • Everything You Need To Know To Start Programming 64-Bit Windows Systems — MSDN article providing a nice overview.
  • The history of calling conventions, part 5: amd64 — an article by the prolific Windows programming evangelist Raymond Chen.
  • Why does Windows64 use a different calling convention from all other OSes on x86-64? — an interesting discussion of the question that just begs to be asked.
  • Challenges of Debugging Optimized x64 code — focuses on the «debuggability» (and lack thereof) of compiler-generated x64 code.

Блог GunSmoker-а (переводы)

. when altering one’s mind becomes as easy as programming a computer, what does it mean to be human.

суббота, 21 февраля 2015 г.

Это перевод FPO. Автор: Ларри Остерман.

На прошлой неделе я болтал с одним парнем из команды измерения производительности. Он мне рассказал историю, которая меня очень удивила. Как оказалось, они обнаружили проблему производительности в одном стороннем драйвере. К сожалению, у них возникли проблемы в локализации узкого места, поскольку разработчик драйвера скомпилировал его с FPO (Frame Pointer Ommission) и не предоставил отладочную информацию.

Что тут удивительного? Ну, я удивился что сегодня вообще кто-то использует FPO.

Но что такое FPO?

Чтобы понять ответ, нам нужно вернуться назад во времени.

Процессор Intel 8088 имел чрезвычайно мало регистров, вот они (я игнорировал сегментные регистры):

Даже в таком ограниченном наборе регистров этим регистрам были заданы специальные роли. Регистры AX , BX , CX и DX были регистрами «общего назначения», SI и DI были «индексными» регистрами, SP был «указателем стека», BP был «указателем фрейма» («указателем базы»), IP был «указателем инструкции», а FLAGS был регистром только для чтения, который содержал информацию о текущем состоянии процессора.

Регистры BX , SI , DI и BP были специальными, потому что они могли использоваться как «индексные» регистры. Индексные регистры чрезвычайно важны для компилятора, поскольку только они могут использоваться в получении доступа к памяти через указатель. Другими словами, если у вас есть структура, расположенная по смещению $1234 в памяти, вы можете установить индексный регистр в значение $1234 и получать доступ к значениям, относительно этого адреса (и регистра). Например:
Мы записываем в регистр BX значение памяти, на которое указывает [Structure] , а затем записываем в регистр AX слово, находящееся в 4 байте с начала этой структуры.

Здесь нужно отметить, что регистр SP не являлся индексным регистром. Это означало, что для получения доступа к локальным переменным и аргументам на стеке вам нужно использовать другой регистр — и именно так появился регистр BP . Индексный регистр BP был создан специально для получения доступа к значениям в стеке.

Когда вышел 386, разработчики Intel расширили регистры до 32 бит, а также сняли ограничение, что только BX , SI , DI и BP могли использоваться как индексные:

Это неплохо: неожиданно, вместо жалких трёх регистров, компилятор мог использовать все шесть.

Поскольку индексные регистры используются для доступа к полям структур, для компилятора они на вес золота — чем их больше, тем лучше, так что стоит пойти на многое, лишь бы их было побольше.

Некий очень умный человек сообразил, что раз теперь ESP является индексным регистром, то EBP больше не является единственным специально выделенным регистром для стека. Другими словами, вместо: вы могли получить доступ к первому параметру на стеке следующим образом ( EBP + 0 — старое значение EBP , EBP + 4 — адрес возврата): Это работает на отлично, теперь EBP освобождается и его можно использовать как дополнительный регистр общего назначения! Такая оптимизация в компиляторе называется «Frame Pointer Omission», известная под акронимом FPO.

Но с FPO есть небольшая проблема.

Если вы посмотрите на первый вариант кода (без FPO), то заметите, что первой инструкцией в функции будет PUSH EBP , за которым будет MOV EBP, ESP . Здесь получается интересный и крайне полезный (побочный) эффект. Фактически, при этом получается односвязный список, в котором хранятся указатели на каждый фрейм для всех вызывающих функции. Таким образом, зная значение EBP внутри одной функции, вы можете получить стек вызовов для этой функции. Это невероятно полезно для отладки, потому что это означает, что стеки вызовов всегда точны — даже если у вас на руках нет отладочной информации. К сожалению, если вы начинаете использовать FPO, список фреймов теряется — эта информация просто не отслеживается.

Чтобы решить эту проблему, информация, которая теряется при использовании FPO, заносится в отладочную информацию. Таким образом, если у вас есть отладочная информация («символы»), то вы сможете построить стек вызовов.

FPO был включен для всех модулей Windows, начиная с NT 3.51. Но начиная с Windows Vista FPO был отключен — поскольку он стал не нужен: машины стали значительно быстрее машин 1995 года, так что небольшой выигрыш в производительности от FPO не покрывал его пенальти на отладку и анализ.

Попытка понять параметр gcc -fomit-frame-pointer

Я попросил Google дать мне значение опции gcc -fomit-frame-pointer , которая перенаправляет меня на следующую инструкцию.

В соответствии с моими знаниями для каждой функции в стеке памяти процесса будет создана запись активации, чтобы сохранить все локальные переменные и некоторую дополнительную информацию. Я надеюсь, что этот указатель кадра означает адрес записи активации функции.

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

Читать еще:  Powerpoint расширение файла

Для большинства меньших функций не требуется указатель кадра — большие функции МОГУТ нуждаться в этом.

Это действительно о том, насколько хорошо компилятору удается отслеживать, как используется стек, и где вещи находятся в стеке (локальные переменные, аргументы, переданные текущей функции и аргументы, готовые к функции, подлежащей вызову). Я не думаю, что легко охарактеризовать функции, которые нуждаются или не нуждаются в указателе кадра [технически, NO function HAS имеет указатель на фрейм — это скорее случай «если компилятор считает необходимым уменьшить сложность другого кода» ].

Я не думаю, что вам следует «попытаться сделать функции не имеющими фрейм-указатель» как часть вашей стратегии кодирования — как я уже сказал, простые функции им не нужны, поэтому используйте -fomit-frame-pointer, и вы получите еще один регистр для распределителя регистров и сохраните 1-3 инструкции по входу/выходу на функции. Если вашей функции нужен фрейм-указатель, это потому, что компилятор решает, что лучший вариант, чем использование указателя кадра. Это не цель иметь функции без указателя рамки, это цель иметь код, который работает как правильно, так и быстро.

Обратите внимание, что «не имеющий framepointer» должен давать лучшую производительность, но это не какая-то волшебная пуля, которая дает огромные улучшения, особенно не на x86-64, у которой уже есть 16 регистров. В 32-разрядном x86, поскольку у него только 8 регистров, один из которых является указателем на стек, а другой — в качестве указателя фрейма, берется 25% пространства регистров. Чтобы изменить это до 12,5%, это довольно улучшилось. Разумеется, компиляция для 64-битного кода тоже очень поможет.

Это все о регистре BP/EBP/RBP на платформах Intel. Этот регистр по умолчанию имеет сегмент стека (для доступа к сегменту стека не требуется специальный префикс).

EBP — лучший выбор регистра для доступа к структурам данных, переменным и динамически распределенным рабочим пространствам в стеке. EBP часто используется для доступа к элементам в стеке относительно неподвижной точки в стеке, а не относительно текущего TOS. Он обычно идентифицирует базовый адрес текущего кадра стека, установленного для текущей процедуры. Когда EBP используется в качестве базового регистра при вычислении смещения, смещение рассчитывается автоматически в текущем сегменте стека (т.е. Сегменте, выбранном в настоящее время SS). Поскольку SS не требуется явно указывать, кодирование команд в таких случаях является более эффективным. EBP также может использоваться для индексирования в сегменты, адресуемые через другие регистры сегментов.

Так как на большинстве 32-битных платформ сегмент данных и сегмент стека совпадают, эта связь EBP/RBP со стеком уже не является проблемой. Так же на 64-битных платформах: архитектура x86-64, представленная AMD в 2003 году, в значительной степени снизила поддержку сегментации в 64-битном режиме: четыре из сегментных регистров: CS, SS, DS и ES вынуждены 0 Эти обстоятельства 32-разрядных и 64-разрядных платформ x86 по существу означают, что в инструкциях процессора, которые обращаются к памяти, можно использовать регистр EBP/RBP без префикса.

Таким образом, параметр компилятора, о котором вы писали, позволяет использовать BP/EBP/RBP для других средств, например. для хранения локальной переменной.

В разделе «Это позволяет избежать инструкций по сохранению, настройке и восстановлению указателей кадров» означает, что для ввода каждой функции не требуется следующий код:

или enter , что очень полезно для процессоров Intel 80286 и 80386.

Кроме того, перед возвратом функции используется следующий код:

или инструкции leave .

Инструменты отладки могут сканировать данные стека и использовать эти данные с нажатой EBP-регистрами при поиске call sites , т.е. отображать имена функции и аргументы в том порядке, в котором они были названы иерархически.

У программистов могут быть вопросы о кадрах стека не в широком смысле (что это единственный объект в стеке, который обслуживает только один вызов функции и сохраняет обратный адрес, аргументы и локальные переменные), но в узком смысле — когда термин stack frames упоминается в контексте параметров компилятора. С точки зрения компиляторов фрейм стека — это только код входа и выхода для подпрограммы, который подталкивает привязку к стеку, который также может использоваться для отладки и обработки исключений. Средства отладки могут сканировать данные стека и использовать эти привязки для обратного трассировки, а расположение call sites в стеке, т.е. Отображать имена функции в том порядке, в котором они были названы иерархически.

Вот почему очень важно понять для программиста, что фрейм стека в терминах параметров компилятора — потому что компилятор может контролировать, сгенерировать этот код или нет.

В некоторых случаях кадр стека (код ввода и выхода для подпрограммы) может быть опущен компилятором, и переменные будут напрямую доступны через указатель стека (SP/ESP/RSP), а не удобный указатель базы (ВР/ESP/РСП). Условия для компилятора для опускания кадров стека для некоторых функций могут быть разными, например: (1) функция является функцией листа (т.е. Конечной сущности, которая не вызывает другие функции); (2) не используются исключения; (3) не вызываются вызовы с исходящими параметрами в стеке; (4) функция не имеет параметров.

Опускание фреймов стека (код входа и выхода для подпрограммы) может сделать код меньше и быстрее, но также может отрицательно повлиять на способность отладчиков отслеживать данные в стеке и отображать их для программиста. Это параметры компилятора, которые определяют, в каких условиях функция должна удовлетворять, чтобы компилятор мог присвоить ему код записи стека и код выхода. Например, у компилятора могут быть опции для добавления такого кода входа и выхода в функции в следующих случаях: (a) всегда, (b) никогда, (c) при необходимости (с указанием условий).

Возвращаясь из общих черт к особенностям: если вы будете использовать параметр компилятора -fomit-frame-pointer GCC, вы можете выиграть как код входа и выхода для этой процедуры, так и наличие дополнительного регистра (если он уже не включен по умолчанию либо сам по себе, либо неявно другими вариантами, в этом случае вы уже выиграли от выигрыша от использования регистра EBP/RBP, и никакой дополнительной выгоды не будет получено путем явного указания этого параметра, если он уже неявно). Обратите внимание, однако, что в 16-битном и 32-битном режимах регистр BP не имеет возможности доступа к 8-битным частям, например AX (AL и AH).

Поскольку этот параметр, кроме того, что позволяет компилятору использовать EBP в качестве универсального регистра при оптимизации, также предотвращает создание кода выхода и ввода для фрейма стека, что усложняет отладку — поэтому Документация GCC явно заявляет (необычно подчеркивает жирным шрифтом), что включение этой опции делает невозможным отладку на некоторых машинах

Также помните, что другие параметры компилятора, связанные с отладкой или оптимизацией, могут неявно включать или отключать опцию -fomit-frame-pointer .

Я не нашел никакой официальной информации на gcc.gnu.org о том, как другие параметры влияют на -fomit-frame-pointer на платформах x86, https://gcc.gnu.org/onlinedocs/gcc-3.4.4/gcc/Optimize-Options.html указывает только следующее:

-O также включает -fomit-frame-pointer на машинах, где это не мешает отладке.

Таким образом, из документации, как таковой, не ясно, будет ли включен -fomit-frame-pointer , если вы просто скомпилируете с помощью одного параметра -O на платформе x86. Он может быть протестирован эмпирически, но в этом случае от разработчиков GCC нет обязательств не изменять поведение этого параметра в будущем без уведомления.

Однако Peter Cordes указал в комментариях, что существует разница в настройках по умолчанию для -fomit-frame-pointer между платформами x86-16 и x86-32/64 платформы.

Эта опция — -fomit-frame-pointer — также относящаяся к Intel С++ Compiler 15.0, а не только к GCC:

Для компилятора Intel этот параметр имеет псевдоним /Oy .

Вот что написал об этом intel:

Эти параметры определяют, используется ли EBP в качестве универсального регистра при оптимизации. Параметры -fomit-frame-pointer и /Oy позволяют это использовать. Параметры -fno-omit-frame-pointer и /Oy -disallow it.

Некоторые отладчики ожидают, что EBP будет использоваться как указатель фрейма стека и не сможет создать обратную трассировку стека, если это не так. -fno-omit-frame-pointer и /Oy -options направляют компилятор на создание кода, который поддерживает и использует EBP в качестве указателя фрейма стека для всех функций, чтобы отладчик все же мог создавать обратную трассировку стека, не выполняя следующие действия:

Для -fno-omit-frame-pointer: отключить оптимизацию с -O0 Для /Oy -: выключение/O1,/O2 или /O 3 оптимизации Опция -fno-omit-frame-pointer устанавливается, когда вы указываете опцию -O0 или -g. Параметр -fomit-frame-pointer устанавливается, когда вы указываете опцию -O1, -O2 или -O3.

Параметр/Oy устанавливается, когда вы указываете опцию /O 1,/O2 или/O3. Опция /Oy — устанавливается, когда вы указываете опцию /Od.

Использование опции -fno-omit-frame-pointer или /Oy — уменьшает количество доступных регистров общего назначения на 1 и может привести к чуть менее эффективному коду.

ПРИМЕЧАНИЕ Для систем Linux *: В настоящее время проблема с обработкой исключений GCC 3.2. Поэтому компилятор Intel игнорирует этот параметр, когда GCC 3.2 установлен для С++, а обработка исключений включена (по умолчанию).

Помните, что приведенная выше цитата применима только для компилятора Intel С++ 15, а не для GCC.

Ссылка на основную публикацию
ВсеИнструменты
Adblock
detector