Виртуализация исполнения машинного кода процессорной архитектуры ARM в Android-x86 окружении (1187396), страница 2
Текст из файла (страница 2)
Каждая операция реализована ввиде небольшого блока кода на С. Далее этот блок компилируется вобъектный файл. Количество микроопераций выбрано так, чтобы их общеечисло было небольшим, и в настоящий момент это количество не превышаетдвухсотен,чтогораздоменьшепо сравнению с классическимипроцессорными архитектурами (к примеру ARM насчитывает более 500машинных инструкций).Далее утилита под названием dyngen, используя в качестве входныхданныхобъектныйфайл,содержащиймикрооперациигенерируетдинамический генератор кода. Он будет вызываться во время работыгостевого кода для того, чтобы сгенерировать конкретную необходимуюфункцию, прежде разбитую на микрооперации.8Ключевой идеей в данном случае является то, что на момент трансляциине известны значения параметров, которые будут передаваться в функции.Таким образом объектные файлы, сгенерированные компилятором содержатуказатели на места, где эти параметры будут использоваться.
Это позволяетdyngen определить их и сформировать корректный динамический генераторкода, который в свою очередь в время работы гостевого кода подставивнеобходимыезначенияпараметровигенерируеткорректнуюпоследовательность машинных инструкций в архитектуре хостовой системы.Утилита dyngen является ключевой для процесса трансляции в QEMU.Над каждым из объектных файлов с микрооперациями производятсяследующие действия:1.
Объектный файл разбирается для получения таблицы символов, атакже для определения вхождения всех параметров, значения которыхнеизвестны. Этот этап сильно зависит от типа исполняемого файла, а он всвою очередь от гостевой платформы (dyngen поддерживает форматы ELF(Linux), PE-COFF (Windows) и MACH-O (Mac OS X))2. В объектном файле с помощью таблицы символов выделяется блокмашинных инструкций, реализующий транслируемую функцию. Пролог иэпилог функции, как правило, опускается.3. Далее для каждого параметра, найденного из таблицы символовобъектного файла, формируется так называемая релокация – запись особогорода, которая будет заменена на адреса виртуальных регистров привыполнении этого блока кода.4. Также для некоторых процессорных архитектур, таких как ARM,важно, чтобы параметры находились близко в адресном пространствеотносительно кода, который их использует.
Это происходит по той причине,9что код получает к ним доступ не по абсолютным адресам, а поотносительному смещению счетчика командРассмотрим подробнее как QEMU работает с оттранслированнымиблоками. При первом вхождении гостевого кода в функцию, эмулятортранслирует её, что позволяет сразу же выполнять инструкции нативно.Каждый такой блок помещается в кэш размером 16Мб, что позволяет далеебыстрее находить и выполнять блоки нативного кода, которые используютсячасто.Также QEMU использует статически фиксированный регистровыйаллокатор. Это значит, что каждый регистр целевой процессорнойархитектуры статически отображен на регистр или адрес хостовой системы.В большинстве случаев эмулятор просто отображает целевые регистры напамять, так было сделано в угоду простоте и переносимости.Далее, как только блок машинных инструкций целевой системы былвыполнен, эмулятор обращается к виртуальному процессору для нахожденияследующего адреса исполнения (например это необходимо когда следующаяинструкция – это ветвление).
Исходя из этого ищется и исполняетсяследующий транслированный блок гостевого кода.3. Постановка задачЦельработы–предоставитьвозможностьисполненияAndroid-приложений, содержащих нативный код, скомпилированный подархитектуру ARM, на системах, базирующихся на архитектуре x86 подуправлением Android.10Данная работа была выполнена на базе кроссплатформенного эмуляторасистемы на базе процессора с архитектурой ARMv7.
Использованныйэмулятор осуществлял виртуализацию не только виртуального процессора,но также памяти и устройств.Однако для эффективного исполнения Android-приложений, использующиенативный код не требуется эмуляция всей системы. Был сделан вывод онеобходимости реализации так называемой user-mode виртуализации – когдаиспользуется часть эмулятора, отвечающая за процессор. Она используетсятолько для исполнения нативной части кода Android-приложения, основнаячасть которого в свою очередь исполняется хостовой Java-машиной.Таким образом для достижения цели данной работы были выполненыследующие задачи:1. Преобразование существующего эмулятора с целью позволить запускотдельных программ, скомпилированных под ARM на процессоре сархитектурой x86.
Это позволило удовлетворить цели работы в первомприближении,показатьпринципиальнуювозможностьиспользуемогоподхода.2. Написание загрузчика, необходимого для корректногозапускаисполняемого файла, а также для его связывания (линковки) с внешнимибиблиотеками. За счет данного этапа появилась возможность рассмотретьработу реальных приложений, так как в своей основной массе программы какправило используют внешние библиотеки. В качестве основной библиотеки,на которой производилось тестирование была выбрана стандартная11библиотека языка С, по причине ее широкой распространенности, а такжеприсутствия на всех платформах.3. Реализация оптимизаций в линкере, позволяющих вместо исполнениякода внешних библиотек через эмулятор сделать так называемый пробросвызовов в хостовую систему, с тем, чтобы эти вызовы исполнить нативно.Этотэтаппозволилизбавитьсяотнеобходимостисопровождатьисполняемый файл всеми ARM-версиями библиотек, от которых он зависит.Такжеблагодаряэтомусталовозможнымполучитьприроствбыстродействии.Несмотря на то, что в целом данная работа выполнена на базеэмулятора, сходного по своей структуре с QEMU, имеется ряд важныхотличий, что наделяет использованное решение самостоятельной ценностью:1.
В отличие от QEMU, заточенного под исполнение десктопногобинарного кода, используемый эмулятор изначально разрабатывался такимобразом, чтобы иметь возможность быть встроенным в Android на уровневиртуальной машины.2. Вторым серьёзным отличием является оптимизация, за счёт которойотпала необходимость в поддержании двух видов зависимостей (гостевых ихостовых), а также снизилось время ожидания при старте, так как сталовозможным использовать стандартную, отлаженную и оптимизированнуюутилиту линковки, родную для хостовой системы.4.
Возможные подходы виртуализации4.1 Структура эмулятора12Данный раздел посвящен общей структуре используемого эмулятора.Здесь представлены его составные части, дано краткое описание каждой изних. Также будет рассмотрен цикл эмуляции на примере одной из команд.Главной частью пожалуй любого эмулятора является виртуальныйпроцессор или vcpu (virtaul central processor unit). В данной реализации онтакже стоит во главе угла и состоит из нескольких составных частей:1. Контекст vcpu.2. Модуль fetch (выборки) и decode (декодирования) инструкций.3.
Набор функций-обработчиков машинных инструкций ARMРассмотрим каждую из приведенных составных частей подробнее.Контекст vcpu.В данной работе упор делался на эмуляцию архитектуры процессоровARMv7, так как именно она на данный момент является наиболеевостребованной на рынке мобильных устройств. Это позволяет очертитьопределенные рамки решаемой задачи, что позволяет упростить некоторыемоменты, связанные с внутренней архитектурой, а также интерпретациисистемы команд.ARM предоставляет 30 регистров общего назначения разрядностью 32бит. В зависимости от режима и состояния процессора пользователь имеетдоступ только к строго определенному набору регистров.
В режиме ARMразработчику постоянно доступны 17 регистров (Рис. 1):1. 13 регистров общего назначения (r0..r12).2. Stack Pointer (r13) — содержит указатель стека выполняемойпрограммы.133. Link register (r14) — содержит адрес возврата в инструкциях ветвления.4. Program Counter (r15) — биты [31:1] содержат адрес выполняемойинструкции.5. CurrentProgramStatusRegister(CPSR)—содержитфлаги,описывающие текущее состояние процессора.
Модифицируется привыполнениимногихинструкций:логических,арифметических,инструкций ветвления и инструкций условного перехода.Во всех режимах, кроме User mode и System mode, доступен также SavedProgram Status Register (SPSR). После возникновения исключения регистрCPSR сохраняется в SPSR. Тем самым фиксируется состояние процессора(режим,состояние;флагиарифметических,логическихопераций,разрешения прерываний) на момент непосредственно перед прерыванием [8].14Рис. 1.
Регистры ARMv7.Таким образом контекст vcpu содержит в себе все необходимые банкирегистров, также подмодули сопроцессоров, необходимые для вычислений сплавающей точкой и simd-инструкций (single instruction multiple data).Модуль fetch (выборки) и decode (декодирования) инструкций.Каждая инструкция машинного кода, поданная рассматриваемомуэмулятору на вход, проходит так называемый конвейер, повторяющий в15упрощенном виде таковой конвейер исполнения в реальных процессорах. Онможет быть представлен следующей краткой схемой:Fetch → Decode → EmulationТаким образом первым этапом исполнения машинной инструкциигостевого кода ARM становится её выборка. ARMv7 поддерживаетгарвардскую архитектуру, то есть данные и код в программе хранятсяраздельно[9].Этозначительноупрощаетэмуляцию,позволяя незадумываться о том как интерпретировать входящую последовательностьбайт – как данные или инструкции.Также следует отметить, что в настоящий момент архитектура ARMподдерживает несколько наборов команд:1.
ARM. Полноценный набор команд, практически все из нихподдерживают условное выполнение, а также все команды имеют размер 4байта и выровнены в памяти строго на 4 байта (Рис. 2).2. Thumb. Укороченный набор инструкций, повышающий плотностькода и экономящий память. Размер инструкции равен 2-м байтам, которыевыровнены также на 2 байта.3. Thumb-2. Расширение Thumb. Он расширяет ограниченный 16-битныйнабор команд Thumb дополнительными 32-битными командами, чтопозволяет выполнятьнесколько Thumb инструкций с помощью Thumb-2инструкции, чтобы повысить плотность кода [10]. Команды могут иметьразмер как 4, так и 2 байта с выровнены на 2 байта.Исходя из наличия различных наборов команд различного размера, на16этапе выборки эмулятору необходимо знать, в каком состоянии находитсяvcpu (ARM state/Thumb state).