Что означают адреса в трассировках стека DMD? - PullRequest
0 голосов
/ 06 февраля 2019

Я компилирую файл stacktrace.d: void main(){assert(false);} с отключенным ASLR и при запуске получаю:

core.exception.AssertError@stacktrace.d(2): Assertion failure
----------------
??:? _d_assertp [0x55586ed8]
??:? _Dmain [0x55586e20]

objdump -t stacktrace|grep _Dmain дает

0000000000032e0c w F .text 0000000000000019 _Dmain

И если я запускаю gdb -q -nx -ex start -ex 'disas /rs _Dmain' -ex q stacktrace:

...
Dump of assembler code for function _Dmain:
   0x0000555555586e0c <+0>: 55  push   %rbp
   0x0000555555586e0d <+1>: 48 8b ec    mov    %rsp,%rbp
=> 0x0000555555586e10 <+4>: be 02 00 00 00  mov    $0x2,%esi
   0x0000555555586e15 <+9>: 48 8d 3d 44 c0 02 00    lea    0x2c044(%rip),%rdi        # 0x5555555b2e60 <_TMP0>
   0x0000555555586e1c <+16>:    e8 47 00 00 00  callq  0x555555586e68 <_d_assertp>
   0x0000555555586e21 <+21>:    31 c0   xor    %eax,%eax
   0x0000555555586e23 <+23>:    5d  pop    %rbp
   0x0000555555586e24 <+24>:    c3  retq   

Таким образом, даже если первые два байта 0x55 были просто обрезаны, 0x ... 86e20, заданные в трассировке стека, не совпадают с началоминструкция.

1 Ответ

0 голосов
/ 07 февраля 2019

ОК, я только что нашел часть исходного кода, которая подтверждает мои интуитивные ощущения из комментария.

Вот вина Git за то, когда он был добавлен: https://github.com/dlang/druntime/blame/bc940316b4cd7cf6a76e34b7396de2003867fbef/src/core/runtime.d#L756

Увы,сообщение коммита не суперинформативно, но сам код вместе с моей памятью очень меня убедил.

Так что это файл core/runtime.d в библиотеке druntime.На момент написания этой статьи она находилась в строке 756

enum CALL_INSTRUCTION_SIZE = 1; // it may not be 1 but it is good enough to get
   // in CALL instruction address range for backtrace
callstack[numframes++] = *(stackPtr + 1) - CALL_INSTRUCTION_SIZE;

Обратите внимание, что переменная callstack создает копию текущих вызовов при возникновении исключения.При запросе на фактическую запись принтер трассировки будет смотреть на этот массив, чтобы определить, что писать.(Видите, ДЕЙСТВИТЕЛЬНО МЕДЛЕННО искать отладочную информацию для печати номеров файлов / строк и имен функций, поэтому он делает это только тогда, когда это необходимо, чтобы сохранить нормальное использование исключений - когда оно генерируется и перехватывается позже - быстрее.)

Во всяком случае, я помню, когда использовалась обратная трассировка для печати неправильной строки.Это напечатало бы строку кода, содержащую следующую инструкцию - которая может быть довольно далеко в исходном тексте от фактического утверждения assert / throw, делая печать менее полезной.Если вы посмотрите на эту ссылку git blame, вы увидите старый код, используемый для буквального копирования адресов прямо из стека.

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

Новый код немного перематывает этот адрес, чтобы вернуть его к самой инструкции вызова - таким образом, помещая напечатанный кодФункция на линии, где он принадлежит.Но на x86 есть несколько разных инструкций вызова, и я даже не уверен, что можно правильно перемотать назад - вы можете определить реальный размер инструкции только по коду операции иВы знаете только, где находится код операции, если знаете размер инструкции или читаете код в прямой последовательности, как это делает сам процессор.Более того, на других процессорных архитектурах размер будет другим.

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

Таким образом, он значительно упрощает реализацию, просто предполагая, что размер равен 1 - достаточно хорош, чтобы вернуть его в эту границу.

Бьюсь об заклад, GDB делает что-то аналогично внутри, просто его принтер скрывает это, показывая адрес возврата из стека непосредственно в его обратной трассировке.(Кстати, забавный совет: передайте --DRT-trapExceptions=no аргументам командной строки вашей программы при запуске ее внутри GDB. Затем она будет перехватывать точку выброса при работающей программе вместо того, чтобы печатать сообщение и говорить, что программа завершилась с кодом 1!)

Код печати druntime также может +1 возвращаться к нему перед печатью, чтобы скрыть этот внутренний взлом реализации ... но ме.Обратный адрес также не там, где на самом деле был звонок, вам нужно посмотреть выше в вашем дизассемблере.И даже GDB на самом деле не показывает адрес вызова (по крайней мере, не моя старая версия этого, может быть, новые показывают).Но было бы неплохо, если бы в разборке было значение для разборки, независимо от ... Если вы хотите сделать пиар в пьяное время, я бы поддержал вас в этом (заметьте, у меня нет полномочий, но я могу помочь с комментариями).

Но это, по крайней мере, однозначно объясняет статус-кво.

...