Даже при оптимизированном указателе фреймов стековые кадры часто можно различить, просматривая в стеке сохраненные адреса возврата . Помните, что последовательность вызова функции в x86 всегда состоит из:
call someFunc ; pushes return address (instr. following `call`)
...
someFunc:
push EBP ; if framepointer is used
mov EBP, ESP ; if framepointer is used
push <nonvolatile regs>
...
так что ваш стек всегда - даже если указатели фреймов отсутствуют - там будут адреса возврата.
Как узнать обратный адрес?
- для начала, на x86, инструкции имеют разную длину. Это означает, что адреса возврата - в отличие от других указателей (!) - имеют тенденцию быть смещенными значениями. Статистически 3 / 4 из них заканчиваются не кратными четырем.
Любой неправильный указатель является хорошим кандидатом на обратный адрес.
- затем, помните, что
call
инструкции для x86 имеют определенные форматы кода операции; прочитайте несколько байтов до обратного адреса и проверьте, есть ли там код операции call
(в большинстве случаев 99%, это пять байтов назад для прямого вызова и три байта назад для вызова через регистр). Если это так, вы нашли обратный адрес.
Это также способ отличить v ++ таблицы V ++ от адресов возврата по пути - точки входа vtable, которые вы найдете в стеке, но, оглядываясь назад, по тем адресам, которые вы не найдете call
в инструкциях.
С помощью этого метода вы можете получить кандидатов на последовательность вызовов из стека, даже не имея символов, отладочной информации размера кадра или чего-либо еще.
Подробности того, как собрать фактическую последовательность вызовов вместе из этих кандидатов, менее просты, хотя вам нужен дизассемблер и некоторая эвристика для отслеживания потенциальных потоков вызовов от самого низкого найденного адреса возврата вплоть до последней известной программы. место нахождения. Может быть, однажды я напишу об этом в блоге ;-), хотя в этот момент я бы скорее сказал, что поле для публикации в потоке стека слишком мало, чтобы содержать это ...