Как узнать, находится ли указатель в стеке на ПК / Visual C ++ - PullRequest
2 голосов
/ 03 декабря 2008

[Это специально для ПК / Visual C ++ (хотя любые другие ответы будут весьма полезными:))]

Как вы можете определить, приходит ли указатель от объекта в стеке? Например:

int g_n = 0;

void F()
{
    int *pA = &s_n;
    ASSERT_IS_POINTER_ON_STACK(pA);
    int i = 0;
    int *pB = &i;
    ASSERT_IS_POINTER_ON_STACK(pB);
}

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

Спасибо! RC

Ответы [ 9 ]

2 голосов
/ 03 декабря 2008

Я задам второй вопрос - ПОЧЕМУ нужно знать? Ничего хорошего из этого не получится.

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

static void * markerTop = NULL;

int main()
{
    char topOfStack;
    markerTop = &topOfStack;
    ...
}

bool IsOnStack(void * p)
{
    char bottomOfStack;
    void * markerBottom = &bottomOfStack;
    return (p > markerBottom) && (p < markerTop);
}
2 голосов
/ 03 декабря 2008

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

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

Один из способов получить текущий указатель стека - использовать встроенную сборку:

uint32_t GetESP(void)
{
    uint32_t ret;
    asm
    {
        mov esp, ret
    }
    return ret;
}

Остерегайтесь встраивания и оптимизации! Оптимизатор может взломать этот код.

1 голос
/ 06 декабря 2008

Поскольку вы указали Visual C и подтвердили, я собираюсь предположить, что вы можете использовать отладочную сборку. В этом случае вы можете воспользоваться заборами, которые этот конкретный компилятор ставит для проверки памяти:

#define IS_POINTER_TO_STACK(vp)   (*((int*)(vp)-1)==0xCCCCCCCC)

правильно работал во всех этих случаях в отладочной сборке:

#define ASSERT(v)  printf("assert: %d\n", v);  //so it doesn't really quit
int g_n = 0;
void test_indirectly(void* vp) {
    ASSERT(IS_POINTER_TO_STACK(vp));
}
void F() {
    int *pA = &g_n;
    ASSERT(IS_POINTER_TO_STACK(pA));         //0

    int i = 0;
    int j = 0;
    int *pB = &i;
    ASSERT(IS_POINTER_TO_STACK(pB));         //1
    ASSERT(IS_POINTER_TO_STACK(&j));         //1

    int *pC = (int*)malloc(sizeof(int));
    ASSERT(IS_POINTER_TO_STACK(pC));         //0
    free(pC);
    ASSERT(IS_POINTER_TO_STACK(pC));         //0
    pC = new int;
    ASSERT(IS_POINTER_TO_STACK(pC));         //0
    delete pC;

    char* s = "HelloSO";
    char w[6];
    ASSERT(IS_POINTER_TO_STACK("CONSTANT")); //0
    ASSERT(IS_POINTER_TO_STACK(s));          //0
    ASSERT(IS_POINTER_TO_STACK(&w[0]));      //1
    test_indirectly(&s);                     //1

    int* pD; //uninit
    ASSERT(IS_POINTER_TO_STACK(pD));    //runtime error check

}

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

Это работает только в сборке отладки - сборка выпуска сообщает о ложном для всех из них.

1 голос
/ 03 декабря 2008

Технически говоря, в портативном C вы не можете знать. Стек аргументов - это аппаратная деталь, которая присуща многим, но не всем компиляторам. Некоторые компиляторы будут использовать регистры для аргументов, когда они могут (т.е. fastcall).

Если вы работаете конкретно с Windows NT, вы хотите отобрать у блока исполнения потоков вызов NtCurrentTeb (). В блоге Джо Даффи есть информация об этом, и из него вы можете получить диапазон стеков. Вы проверяете, находится ли указатель в диапазоне, и вы должны хорошо идти.

0 голосов
/ 04 декабря 2008

Хорошо, что касается "почему":

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

0 голосов
/ 04 декабря 2008

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

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

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

Портативный? Не за что. Ужасный интерфейс Candy-Machine? Абсолютно.

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

0 голосов
/ 04 декабря 2008

Я не верю, что для Visual C ++ или почти для всего, что работает на современных платформах Windows (или древних 32-битных OS / 2), потому что на этих платформах стек растет динамически, то есть выделяется новая страница стека только когда ваша программа пытается получить доступ к так называемой защитной странице, блоку размером 4 КБ (для 32-разрядной Windows в любом случае) специально созданной памяти в верхней части выделенного в данный момент фрагмента стека. Операционная система перехватывает исключение, которое генерируется, когда ваша программа пытается получить доступ к этой защитной странице, и (1) отображает новую страницу нормального, действительного стека вместо нее над вершиной выделенного в настоящее время стека, и (2) создает другую Защитная страница чуть выше новой вершины, поэтому она может увеличивать стек по мере необходимости позже. ОС делает это до тех пор, пока стек не достигнет своего предела, и обычно этот предел устанавливается очень высоким.

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

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

0 голосов
/ 03 декабря 2008

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

0 голосов
/ 03 декабря 2008

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

void* TopOfStack; // someone must populate this in the first stack frame

bool IsOnTheStack(void* p)
{
  int x;

  return (size_t) p < (size_t) TopOfTheStack &&
         (size_t) p > (size_t) &x;
}

Конечно, это не работает с несколькими потоками, если вы не делаете поток TopOfTheStack локальным.

Оптимизация стека компилятором также может вызвать проблемы.

...