Получить указатель стека в C на Mac OS X Lion - PullRequest
2 голосов
/ 18 октября 2011

Я столкнулся с некоторым странным поведением при попытке получить текущий указатель стека в C (используя встроенный ASM). Код выглядит так:

#include <stdio.h>
class os {
  public:
    static void* current_stack_pointer();
};

void* os::current_stack_pointer() {
  register void *esp __asm__ ("rsp");
  return esp;
}

int main() {
  printf("%p\n", os::current_stack_pointer());
}

Если я скомпилирую код, используя стандартные параметры gcc:

$ g++ test.cc -o test

Создает следующую сборку:

__ZN2os21current_stack_pointerEv:
0000000000000000        pushq   %rbp
0000000000000001        movq    %rsp,%rbp
0000000000000004        movq    %rdi,0xf8(%rbp)
0000000000000008        movq    0xe0(%rbp),%rax
000000000000000c        movq    %rax,%rsp
000000000000000f        movq    %rsp,%rax
0000000000000012        movq    %rax,0xe8(%rbp)
0000000000000016        movq    0xe8(%rbp),%rax
000000000000001a        movq    %rax,0xf0(%rbp)
000000000000001e        movq    0xf0(%rbp),%rax
0000000000000022        popq    %rbp

Если я запускаю полученный бинарный файл, он падает с SIGILL (Illegal Instruction). Однако, если я добавлю небольшую оптимизацию для компиляции:

$ g++ -O1 test.cc -o test

Сгенерированная сборка намного проще:

0000000000000000        pushq   %rbp
0000000000000001        movq    %rsp,%rbp
0000000000000004        movq    %rsp,%rax
0000000000000007        popq    %rbp
0000000000000008        ret

И код работает нормально. Итак, к вопросу; Есть ли более стабильный способ получить указатель стека из кода C в Mac OS X? Этот же код не имеет проблем в Linux.

Ответы [ 3 ]

5 голосов
/ 18 октября 2011

Проблема с попыткой извлечь указатель стека с помощью вызова функции заключается в том, что указатель стека внутри вызываемой функции указывает на значение, которое будет совершенно другим после возврата функции, и, следовательно, вы захватываете адрес местоположение, которое будет недействительным после звонка. Вы также делаете предположение, что на этой платформе компилятором не был добавлен пролог функции (т. Е. Обе ваши функции в настоящее время имеют пролог, где компилятор устанавливает текущую запись активации в стеке для функции, которая изменит значение RSP, которое вы пытаетесь зафиксировать). По крайней мере, при условии, что компилятором не был добавлен пролог функции, вам нужно будет вычесть размер указателя на платформе, которую вы используете, чтобы фактически получить «истинный» адрес, по которому будет находиться стек указывая после возврата из вызова функции. Это связано с тем, что команда сборки call помещает адрес возврата указателя инструкции в стек, а ret в вызываемом вызове выталкивает это значение из стека. Таким образом, внутри вызываемого абонента, по крайней мере, будет инструкция обратного адреса, на которую будет указывать указатель стека, и это местоположение не будет действительным после вызова функции. Наконец, на некоторых платформах (к сожалению, не в x86) вы можете использовать тег __attributes__((naked)) для создания функции без пролога в gcc. Использование ключевого слова inline во избежание пролога не совсем надежно, поскольку оно не вынуждает компилятор встроить функцию ... при определенных уровнях низкой оптимизации вставка не произойдет, и вы снова получите пролог и указатель стека не будет указывать на правильное местоположение, если вы решите взять его адрес в этих случаях.

Если у вас должно быть значение указателя стека, то единственным надежным способом будет использование сборки, следование правилам ABI вашей платформы, компиляция в объектный файл с использованием ассемблера, а затем связывание этого объектного файла с Остальные объектные файлы в вашем исполняемом файле. Затем вы можете представить функцию ассемблера остальной части вашего кода, включив объявление функции в заголовочный файл. Таким образом, ваш код может выглядеть так (при условии, что вы используете gcc для компиляции вашей сборки):

//get_stack_pointer.h
extern "C" void* get_stack_ptr();

//get_stack_pointer.S
.section .text
.global get_stack_ptr

get_stack_ptr:
    movq %rsp, %rax
    addq $8, %rax
    ret
3 голосов
/ 19 октября 2011

Вместо того, чтобы использовать register переменную с ограничением, вам нужно просто написать какой-нибудь явный встроенный ассемблер для извлечения %esp:

static void *getsp(void)
{
    void *sp;
    __asm__ __volatile__ ("movq %%rsp,%0"
    : "=r" (sp)
    : /* No input */);
    return sp;
}

Вы также можете преобразовать это в макрос, используя выражения операторов gcc:

#define GETSP() ({void *sp;__asm__ __volatile__("movl %%esp,%0":"=r"(sp):);sp;})
0 голосов
/ 18 октября 2011

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

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

...