Почему запуск Qemu отличается от собственного запуска?

Что я сделал?
Я запустил qemu-x86_64 -singlestep -d nochain,cpu ./dummy, чтобы сбросить все регистры фиктивной программы после каждой инструкции, и использовал grep для сохранения всех значений RIP в текстовый файл (qemu_rip_dump.txt). Затем я выполнил фиктивную программу с помощью ptrace и сбрасывал значения RIP после каждой инструкции в другой текстовый файл (ptrace_rip_dump.txt). Затем я сравнил оба файла .txt с diff.

Какого результата я ожидал?
Я ожидал, что оба запуска фиктивной программы будут выполнять одни и те же инструкции, поэтому оба файла дампа будут одинаковыми (одинаковые значения рипа и одинаковое количество значений рипа).

Какой результат я на самом деле получил?
Ptrace выдал около 33 500 значений RIP, а Qemu — 29 800 значений RIP. Значения RIP обоих текстовых файлов начинают отличаться от инструкции 240., большинство значений rip идентичны, но ptrace выполняет около 5500 инструкций, qemu не выполняет, а qemu выполняет около 1800 инструкций, ptrace не выполняет, что приводит к разнице примерно в 3700 инструкций. Оба прогона, кажется, выполняют разные вещи во всей программе, например, есть блок из 3500 инструкций из инструкции 26.500-30.000 (очистка?), который выполняется в собственном прогоне, но не qemu.

Какой у меня вопрос
Почему значения RIP не одинаковы на протяжении всего выполнения программы и, самое главное: что мне нужно сделать, чтобы оба запуска были одинаковыми?

Дополнительная информация

  • фиктивная программа была основной функцией, которая возвращает 0, но эта проблема существует в каждом исполняемом файле, который я отследил
  • я пробовал форсировать qemu с помощью компоновщика ld-linux-x86-64.so.2 с -L /lib64/ - это не дало никакого эффекта
  • если я запускаю qemu несколько раз, дампы будут одинаковыми (равное количество и значение RIP), то же самое касается и ptrace

Что происходит, когда вы запускаете одну и ту же программу в двух разных системах?

stark 10.12.2020 15:27

@stark, запускающий код в другой системе, немного меняет количество выполняемых инструкций, но разница между ptrace и qemu остается примерно такой же.

Sbardila 10.12.2020 15:31

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

Peter Maydell 10.12.2020 22:32

Между прочим, если вы действительно не заботитесь о динамическом компоновщике, вы, вероятно, могли бы просто отбросить все значения RIP перед первым insn в main() - я подозреваю, что это с большей вероятностью даст идентичные результаты в обоих случаях, хотя, безусловно, есть гостевые программы это показало бы разницу и после main().

Peter Maydell 10.12.2020 22:33

@PeterMaydell Я использовал ведение журнала qemu in_asm, чтобы выяснить, где возникают различия. я узнал, что первая разница происходит в _dl_aux_init. Другие различия происходят в __tunables_initget_common_indices.constprop.0__libc_start_mainstrchr_ifunctcache_init.part.0_dl_non_dynamic_init__strlen_sse2__mempcpy_sse2_unaligned и __strrchr_sse2

Sbardila 11.12.2020 16:34

Хорошо, так что первая часть этого действительно там, где динамический компоновщик просматривает вспомогательный вектор. Некоторые из других выглядят так, как будто гостевой код смотрит, какие функции поддерживает ЦП — на вашем хост-ЦП есть поддержка SSE2, поэтому гостевая libc выбирает оптимизированные версии функций, таких как strlen и memcpy, которые ее используют, но QEMU не не поддерживает эмуляцию SSE2, поэтому гостевая библиотека использует разные версии.

Peter Maydell 12.12.2020 19:18
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
6
214
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

С программой «ничего не делающей», подобной той, которую вы тестируете, большая часть выполнения будет выполняться в гостевом динамическом компоновщике и libc. Они делают много работы за кулисами, прежде чем ваша программа получит управление, и часть этой работы варьируется между «родным» запуском и запуском «QEMU». Есть два основных источника расхождений, судя по некоторым дополнительным деталям, которые вы даете в комментариях:

  1. Среда, которую QEMU предоставляет гостевому двоичному файлу, не на 100% идентична той, которую предоставляет реальное ядро ​​хоста; он предназначен только для того, чтобы быть «достаточно близким, чтобы правильные гостевые двоичные файлы вели себя разумным образом». Например, гостю передается структура данных, которая называется «вспомогательный вектор ELF»; он содержит информацию, в том числе «какие функции ЦП поддерживаются», «под каким идентификатором пользователя вы работаете» и т. д. Динамический компоновщик выполняет итерацию по этой структуре данных при запуске, поэтому незначительные безобидные различия в том, какие записи находятся в векторе и в каком порядке, вызовут несколько разные пути выполнения в гостевом коде.

  2. Процессор, который эмулирует QEMU, не предоставляет точно те же функции, что и ваш хост-процессор. Например, нет поддержки эмуляции AVX или SSE2. Гостевая библиотека настраивает свое поведение таким образом, чтобы использовать возможности ЦП, когда они доступны, поэтому она выбирает различные оптимизированные версии функций, таких как memcpy() или strlen() под капотом. Поскольку динамический компоновщик в конечном итоге вызовет эти функции, это также приведет к расхождениям в выполнении.

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

большое спасибо за ваше объяснение! Есть ли у вас предложения по подписям, которые я мог бы искать, чтобы узнать больше о том, что libc ведет себя по-другому?

Sbardila 13.12.2020 19:53

Другие вопросы по теме