LINUX.ORG.RU

c/cpp размер локальных данных на стеке

 


1

4

Добрый день!

По идее должна быть возможность при компиляции из С++ получить значение смещения SP для хранения локальных переменных.

т.е. вход в функцию на asm выглядет примерно так:

push    rbp
push    rbx
push    rsi
push    rdi
push    r12
push    r13
push    r14
push    r15
lea     rbp, [rsp-398h]
sub     rsp, 498h
.....
.....
Как программно, на C++ получить значение (в данном случае) 498h которое выбрал компилятор?
Прим.: понятно, что это будет обращение внутри функции для которой необходима эта информация.

Идеальный ответ: ссылка на соответствующую документацию.
И, да, не могу гуглу сформулировать вопрос.
Заранее благодарен.


Разница между адресом первого и последнего аргумента плюс размер последнего (или первого, не помню в каком они там порядке на стек кладутся) аргумента.

Порядок в общем-то неважен, можно просто проверять какой из адресов больше, так что такой способ пойдёт для любых calling convention.

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

То же самое и с локальными переменными. Адреса сравнивай-вычитай, и всё понятно станет.

Stanson ★★★★★
()
Последнее исправление: Stanson (всего исправлений: 2)

А какую задачу вы хотите решить? Возможно, она решается другим способом.

Наиболее соотвестствующе стандарту и относительно портабельно я бы сделал так:


uintptr_t get_my_addr() {
  int a;
  return (uintptr_t)&a;
}

void g(uintptr_t parent_addr) {
  int x = get_my_addr();
  int a[1234];
  printf("My frame size is %llu\n", (long long)(x - parent_addr));
}

void f() {
  uintptr_t x = get_my_addr();
  g(x);
}

vzzo ★★★
()
Ответ на: комментарий от Stanson

С аргументами - куда бы ни шло но с локальными переменными там не все так просто.

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

Плюс тут еще присутствует выравнивание.

vbv
() автор топика

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

mashina ★★★★★
()
Ответ на: комментарий от vzzo

Не уверен. Дело в том что при вызове get_my_addr на стеке объявится адрес возврата плюс сохранение регистров, можно конечно это все inline'ить но кривовато.

И выравнивание локальных переменных. т.е. первая переменная будет размера long long вторая char третья long long причем описанная после обращения к (x) тогда придется опираться на то, что все переменные должны быть объявлены до вызова get_my_addr() в g(uintptr).

По идее для этих целей должно существовать некоторое обращение к компилятору типа #def в чем компилятор сообщает размер локальных переменных на стеке для текущей функции.
Как-то так.

Задача стоит следующая нужно получить адрес куда пойдет управление по ret. И не следить за тем где на стеке будет лежать этот адрес. А так-как вход в функцию компилятор оформляет самостоятельно - то эта цифра будет менятся (если добавил еще локальную переменную) а надо глянуть в вызывающий код. При выходе из подпрограммы делается add rsp, 498h потом выдергиваются регистры и только тогда ret. Еще бы хотелось получить кол-во регистров сохраненных в стеке.

Сейчас решаю задачу следующим образом:

  1. Пишу код.
  2. Компилирую.
  3. Дизассемблирую.
  4. Проверяю смещение. если не совпадает изменяю в коде и на п.2.

А хотелось бы автоматически.

vbv
() автор топика
Ответ на: комментарий от vbv

Задача стоит следующая нужно получить адрес куда пойдет управление по ret.

6.49 Getting the Return or Frame Address of a Function:

Built-in Function: void * __builtin_return_address (unsigned int level)

    This function returns the return address of the current function, or of one of its callers.

xaizek ★★★★★
()
Ответ на: комментарий от mashina

Ну это уже совсем «Хардкор». Не ужели нет возможности ее где-то подсмотреть.
Может задать вопрос по другому: а существуют ли некие дефы от компилятора которые получают значение во время компиляции. Должны существовать.... Вот только как это спросить у гугля.

Причем все эти отступы будут менятся в зависимости от модели вызова функции.

vbv
() автор топика
Ответ на: комментарий от vbv

Задача стоит следующая нужно получить адрес куда пойдет управление по ret.

А зачем тебе это понадобилось?

i-rinat ★★★★★
()
Ответ на: комментарий от vbv

Задача стоит следующая нужно получить адрес куда пойдет управление по ret

Как я понимаю, это проще сделать встроенным ассемблером (перед этим убедиться, что -fomit-frame-pointer не будет включён, и функция не будет заинлайнена). На x86 адрес, куда возвращаться, будет в [rbp], на это же место cdecl требует возвращать rsp перед ret из функции (что делает add ...).

Еще бы хотелось получить кол-во регистров сохраненных в стеке

Это, мне кажется, лучше из компилятора пытаться выцепить (но не знаю как, в clang, наверно, можно IR как-нибудь раскурочить). Например, кудовский компилятор это умеет показывать из коробки.

vzzo ★★★
()
Ответ на: комментарий от vzzo

Это, мне кажется, лучше из компилятора пытаться выцепить (но не знаю как,

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

в clang, наверно, можно IR как-нибудь раскурочить

В IR этого не будет, он слишком высокоуровневый.

xaizek ★★★★★
()
Ответ на: комментарий от vbv

Я не понял о чём ты, но адрес возврата можно получить так:

#include <stdio.h>

void myfun() {
    void **p;
    asm("movq %%rbp, %0" : "=r"(p));
    printf("Return address is: %p\n", *(p+1));
}

void main() {
    myfun();
}

anonymous
()
Ответ на: комментарий от vbv

Сейчас решаю задачу следующим образом:

Пишу код. Компилирую. Дизассемблирую. Проверяю смещение. если не совпадает изменяю в коде и на п.2.

А хотелось бы автоматически.

ну так можно и автоматически, накидай скриптец :)

anonymous
()
Ответ на: комментарий от vbv

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

В зависимости от компилятора и его опций, часть локальных переменных может быть в регистрах, а не на стеке, но даже в этом случае, размер стека вполне себе вычисляется. Кроме того, если «потрогать» адреса всех локальных переменных, то компилятору придётся расположить их на стеке, а не в регистрах.

Stanson ★★★★★
()
Ответ на: комментарий от anonymous

Не совсем понял. lea rbp, [rsp-398h] rbp подготавливается для работы с аргументами. и почему тогда rbp плюс один адрес будет указывать на адрес возврата.

в конкретно данном примере адрес возврата будет. 8*8 это ведущие push 8 штук запихивают по 8 байт. 1*8 - это начало адреса возврата. rsp+498h+8*8+1*8;

В примере [rsp-398h]+8 должен указывать на адрес возврата - почему?

vbv
() автор топика
Ответ на: комментарий от vbv

Ну обычно rbp указывает на дно фрейма - это такая, своего рода, договорённость, чтобы можно было отслеживать стек вызовов. В по адресу, который в rbp, хранится значение предыдущего rbp и сразу же за ним - адрес возврата (поэтому и +1 указатель). __builtin_return_address(), кстати, в качестве аргумента принимает номер фрейма из которого тебе надо достать адрес возврата. Вот эта фича и бэктрейсы возможны как раз благодаря такой договорённости.

В x86-64, кстати, аргументы кладутся на стэк только если 6 отведённых для этого целочисленных регистров, а также 8 xmm регистров под флоаты уже забиты.

Другое дело, что rbp может тоже использоваться внутри функции и тогда пример с асмом не сработает, но гцц, вроде, таким не грешит.

В примере [rsp-398h]+8 должен указывать на адрес возврата - почему?

А чёрт его знает. Может это какая-то хитро оптимизированная функция так начинается.

anonymous
()
Ответ на: комментарий от Stanson

Разница между адресом первого и последнего аргумента

Разница адресов не для элементов одного массива смысла не имеет.

anonymous
()

А конечном итоге С придумали для того что бьі не забивать голову адресами и стеком, а тьі опять двадцать пять. Иди уроки учи

anonymous
()
Ответ на: комментарий от anonymous

Разница адресов не для элементов одного массива смысла не имеет.

Аргументы, это вообще-то именно массив данных расположенный на стеке перед вызовом функции. Есть даже специальные конструкции для удобства работы с этим массивом - va_arg и всё такое.

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

Stanson ★★★★★
()
Ответ на: комментарий от vbv

Extensions to the C Language Family Пошел читать...

Почитал. Ничего похожего не нашел.
Но нашел встроеные функции. __builtin....
Буду разбираться. Но, как мне кажется, что-то должно быть для получения информации о процессе сборки. Не должно это быть «черным ящиком». А вообще интересно было бы иметь возможность получения информации о:
1. Начале функции.
2. Кол-ве параметров и их общем размере (не для всех моделей вызова)
3. Сколько push'ами забито.
4. Сколько зарезервировано под локальные переменные.

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

vbv
() автор топика
Ответ на: комментарий от anonymous

А нет, неправильно тебя понял

anonymous
()
Ответ на: Extensions to the C Language Family Пошел читать... от vbv

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

Iron_Bug ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.