Как автоматизировать сбор выходных данных с помощью LLDB, если эти данные доступны только при возврате функции C? - PullRequest
0 голосов
/ 14 февраля 2019

Я скомпилировал двоичный файл без какого-либо исходного кода, но я знаю, что он содержит функцию C со следующей сигнатурой

void generateMoreData ( char * destination, long size )

Символ этой функции виден в отладчике LLDB, и я хотел бы захватитьвсе сгенерированные данные.

В настоящее время я знаю, что могу собирать данные следующим образом:

  1. Я установил точку останова break set -n generateMoreData
  2. Как только точка останова достигнута, я проверяюзначения $rdi и $rsi, поскольку ABI x86_64 для System V (используется Linux, BSD, macOS и Solaris) передает первые два аргумента в эти регистры.
  3. Затем я продолжаю, пока функция не вернетиспользуя thread step-out.
  4. Наконец, я могу вывести данные, используя x -c COUNT ADDRESS, где COUNT - это значение $rsi и ADDRESS значение $rdi, как видно на шаге (2)

Это работает нормально, но я хотел бы автоматизировать весь этот процесс и столкнуться с двумя проблемами:

  • Проблема A: Только при входе в функцию $rdi и$rsi содержит значения, которые мне нужны, но не больше, когда функция возвращается, так как эти регистры используются функцией и, таким образом, теряют свои начальные значения.

  • Проблема B: Я могу установить команды ввыполняется при достижении точки останова с использованием break command add, но ониКоманды не могут содержать thread step-out, поскольку эта команда продолжает выполнение, а первая команда, которая продолжает выполнение, останавливает обработку команд точки останова, поэтому любые команды, установленные после этой команды, никогда не выполняются.

Ответы [ 2 ]

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

Более простой способ выяснить адрес для точки останова для шага B состоит в том, чтобы использовать тот факт, что значение pc в родительском кадре всегда является возвращаемым pc для этого кадра.Таким образом, мы можем определить команду точки останова 1.Поскольку вы не используете никакой информации из текущего кадра при достижении второй точки останова, вам все равно, остановитесь ли вы на возврате или сразу после него.

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

break set -G true -n generateMoreData --skip-prologue false
breakpoint name configure SecondBreakpoint -G true -C "x -o generateMoreData.txt --append-outfile -c \`$size\` $destination" -C "break delete SecondBreakpoint"
break command add 1
> expr long $destination = $arg1
> expr long $size = $arg2
> up
> break set -N SecondBreakpoint -a $pc 
> DONE

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

Я также использовал $arg1 и $arg2 вместо $rsi и $rdi.Это просто удобный псевдоним lldb на тот случай, если вы не можете вспомнить, какой именно ...

Кроме того, решить эту проблему становится сложнее, если эту функцию можно вызывать из нескольких потоков.Затем вам нужно получить текущий поток и установить точку останова для конкретного потока.Кроме того, если это будет вызываться рекурсивно, вам придется создавать разные значения $ size и $ destination для возвращаемого фрейма.

Если вы начнете пытаться решать такие проблемы, возможно, лучше использоватьОбратный вызов Python для точек останова.Тогда вместо того, чтобы хранить данные, которые вы хотите напечатать при возврате в процессе, как это делает умное решение Mecki, вы можете сохранить небольшую структуру данных Python, которая запоминает $ size & $ destination для каждой комбинации фрейма / потока, и выполнять правильную печать, когдакаждый звонок возвращается.

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

Общее решение этой проблемы состоит в том, чтобы фактически использовать две точки останова.

Сначала мы решаем проблему A, сохраняя нужные значения в переменных LLDB:

break set -G true -n generateMoreData
break command add 1
> expr long $destination = $rsi
> expr long $size = $rdi
> DONE

-G true гарантируетчто программа продолжается автоматически после выполнения всех команд, и с помощью expr можно сохранить содержимое регистра в переменных, которые мы называем $destination и $size.

. Для решения проблемы B нам нужна еще одна точка останова.по инструкции возврата generateMoreData.В случае, если generateMoreData имеет более одной инструкции возврата, нам понадобится точка останова для каждой из них, но давайте предположим, что это довольно простая функция и имеет только одну точку останова, как это часто бывает.

Сначаланам нужно взломать эту функцию, поэтому мы просто устанавливаем для нее нормальную точку останова и позволяем отладчику поразить ее.Затем мы можем разобрать функцию, используя dis.Вывод может выглядеть примерно так, как показано ниже:

->  0x7fff76be0bac <+0>:   pushq  %rbp
    0x7fff76be0bad <+1>:   movq   %rsp, %rbp
    0x7fff76be0bb0 <+4>:   pushq  %r14
    0x7fff76be0bb2 <+6>:   pushq  %rbx
    0x7fff76be0bb3 <+7>:   subq   $0x40, %rsp
    :
    0x7fff76be0c5f <+179>: popq   %r14
    0x7fff76be0c61 <+181>: popq   %rbp
    0x7fff76be0c62 <+182>: retq   
    0x7fff76be0c63 <+183>: nop

Абсолютные адреса часто бесполезны, так как они могут меняться между двумя запусками программы (например, из-за ASLR, рандомизации расположения адресного пространства), интересных адресовявляются относительными (<+...>).Знание того, что есть возврат в +182, позволяет нам установить точку останова, и таким образом мы можем решить проблему B:

break set -G true -n generateMoreData -R 182
break command add 2
> x -o generateMoreData.txt --append-outfile -c `$size` $destination
> DONE

-R устанавливает относительное смещение в байтах.Захваченные данные записываются в generateMoreData.txt для последующей проверки (-o устанавливает выходной файл и --append-outfile обеспечивает добавление новых данных вместо перезаписи существующих).

Теперь просто запустите программу и в конце вы можете проверить все сгенерированные данные в выходном файле.

...