воскресенье, 15 апреля 2012 г.

Кто меня звал ?

Наверное одна из наиболее часто используемых команд при отладке с использованием GDB это bt/backtrace/where. Этой командой можно раскрутить стек и увидеть каким образом мы пришли к текущему этапу выполнения. Достаточно знать несколько деталей работы со стеком, чтобы получить ту же самую информацию во время работы приложения.

Вызов метода в Objective-C сводится к выполнению Си функции objc_msgSend или нескольких ее вариаций. Сама функция после определения кода который нужно выполнить делает переход к нему, оставляя неизменным стек, т.о. и в выводе GDB не видно objc_msgSend.

Выполнение каждой функции начинается с помещения в стек указателя на стек фрейм предыдущей функции и сохранении фрейма стека текущей:

    push   %ebp 
    mov    %esp,%ebp

Указатель на stack frame хранится в регистре %ebp.

Стек растет от старших адресов к младшим, следующим образом он будет выглядеть при вызове метода -[SomeObject setObj:(NSObject*)arg1]

+------------------------+                |
|    (NSObject*)arg1     |                |
+------------------------+                |
|  указатель на строку   |               \|/
|  selector, "setObj:"   |                '
+------------------------+
|      SomeObject*       |                
+------------------------+
|  %eip, адрес возврата  |
+------------------------+
| %ebp, last stack frame | <- "push %ebp", в начале функции
+------------------------+ ..
                           '`- "mov %esp,%ebp", %ebp указывает сюда

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

static id getSender()
{
  id result = nil;
  __asm(  "push %%ebx\n"
  "push %%ebp\n"
  "mov (%%ebp),%%ebx\n"
  "mov %%ebx,%%ebp\n"
  "mov (%%ebp),%%ebx\n"
  "mov %%ebx,%%ebp\n"
  "mov 8(%%ebp),%%ebx\n"
  "mov %%ebx,%0\n"
  "pop %%ebp\n"
  "pop %%ebx"
  :"=r"(result)
  );
  return result;
}
 Вызов данной функции вернет объект из которого был произведен вызов текущего метода, а с небольшой модификацией можно получить и метод из которого был произведен вызов.

2 комментария:

gavrix комментирует...

Но ведь это сработает только для i386, верно? как насчет arm?

gavrix комментирует...
Этот комментарий был удален автором.