
În cadrul întâlnirii 0x0A DC7831 Pe 16 februarie, am prezentat o comunicare despre principiile de bază ale emulării codului binar și dezvoltarea noastră proprie — emulatorul platformelor hardware .
În articol vom descrie cum să lansăm firmware-ul dispozitivului în emulator, vom demonstra interacțiunea cu debugger-ul și vom efectua o analiză dinamică a firmware-ului.
Povestea
A long time ago in a galaxy far far away
Acum câțiva ani, în laboratorul nostru a apărut necesitatea de a studia firmware-ul unui dispozitiv. Firmware-ul era comprimat, desfăcându-se cu bootloader-ul. Se ocupa de acest lucru într-un mod destul de complicat, mutând repetat datele în memorie. Iar firmware-ul interacționa activ cu perifericele. Și toate acestea pe un nucleu MIPS.
Emulatoarele existente, din motive obiective, nu ne-au satisfăcut, iar noi doream totuși să lansăm codul. Atunci am decis să creăm un emulator propriu, care să facă minimul necesar și să permită desfăcarea firmware-ului principal. Am încercat — a reușit. Ne-am gândit, dar ce ar fi dacă am adăuga periferia, astfel încât să putem rula și firmware-ul principal. Nu a fost prea dureros — și a funcționat și asta. Din nou ne-am gândit și am decis să facem un emulator complet.
În final, am obținut un emulator de sisteme computaționale .

De ce Kopycat?
Este vorba despre un joc de cuvinte.
- copycat (engleză, substantiv [ˈkɒpɪkæt]) — imitator, imitator
- cat (engleză, substantiv [ˈkæt]) — pisică, motan — animalul de companie preferat al unuia dintre creatorii proiectului
- Litera „K” — de la limbajul de programare Kotlin
Kopycat
Câteva obiective clare au fost stabilite la crearea emulatorului:
- posibilitatea de a crea destul de rapid o nouă periferie, modul, nucleu de procesor;
- posibilitatea de a asambla un dispozitiv virtual din module diverse;
- posibilitatea de a încărca în memoria dispozitivului virtual orice date binare (firmware);
- posibilitatea de a lucra cu snapshot-uri (fotografii ale stării sistemului);
- posibilitatea de interacțiune cu emulatorul prin intermediul debugger-ului încorporat;
- un limbaj plăcut și modern pentru dezvoltare.
În cele din urmă, pentru implementare s-a ales Kotlin, arhitectura de tip bus (când modulele se conectează între ele prin intermediul unor autobuze virtuale de date), JSON — ca format de descriere a dispozitivului, și GDB RSP — ca protocol de interacțiune cu debugger-ul.
Dezvoltarea a durat puțin peste doi ani și continuă activ. În acest timp, au fost realizate nucleele de procesor MIPS, x86, V850ES, ARM, PowerPC.
Proiectul crește și a venit vremea să-l prezentăm publicului larg. O descriere detaliată a proiectului va fi realizată mai târziu, iar acum să ne concentrăm pe utilizarea Kopycat.
Pentru cei mai nerăbdători - versiunea promoțională a emulatorului poate fi descărcată de la .
Rinocer în emulator
Amintim că, anterior, pentru conferința SMARTRHINO-2018 a fost creat un dispozitiv de testare „Rinocer” pentru a învăța abilități de inginerie inversă. Procesul de analiză statică a firmware-ului a fost descris în .
Acum să încercăm să adăugăm „dinamică” și să lansăm firmware-ul în emulator.
Ne vor trebui:
1) Java 1.8
2) Python și modulul pentru utilizarea Python în emulator. Pachetul WHL al modulului Jep pentru Windows poate fi .
Pentru Windows:
1)
2)
Pentru Linux:
1) socat
Ca client GDB, se pot folosi Eclipse, IDA Pro sau radare2.
Cum funcționează?
Pentru a executa firmware-ul în emulator, este necesar să „construim” un dispozitiv virtual, care reprezintă un analog al dispozitivului real.
Dispozitivul real („rinocerul”) poate fi arătat pe schema structurală:

Emulatorul are o structură modulară, iar dispozitivul virtual final poate fi descris într-un fișier JSON.
JSON de 105 linii
{
"top": true,
// Numele pluginului ar trebui să fie același cu numele fișierului (sau calea completă de la începutul bibliotecii)
"plugin": "rhino",
// Directorul în care pluginul plasează
"library": "user",
// Parametrii pluginului (parametrii constructorului dacă este versiunea jar-plugin)
"params": [
{ "name": "tty_dbg", "type": "String"},
{ "name": "tty_bt", "type": "String"},
{ "name": "firmware", "type": "String", "default": "NUL"}
],
// Porturile externe ale pluginului
"ports": [ ],
// Busele interne ale pluginului
"buses": [
{ "name": "mem", "size": "BUS30" },
{ "name": "nand", "size": "4" },
{ "name": "gpio", "size": "BUS32" }
],
// Componentele interne ale pluginului
"modules": [
{
"name": "u1_stm32",
"plugin": "STM32F042",
"library": "mcu",
"params": {
"firmware:String": "params.firmware"
}
},
{
"name": "usart_debug",
"plugin": "UartSerialTerminal",
"library": "terminals",
"params": {
"tty": "params.tty_dbg"
}
},
{
"name": "term_bt",
"plugin": "UartSerialTerminal",
"library": "terminals",
"params": {
"tty": "params.tty_bt"
}
},
{
"name": "bluetooth",
"plugin": "BT",
"library": "mcu"
},
{ "name": "led_0", "plugin": "LED", "library": "mcu" },
{ "name": "led_1", "plugin": "LED", "library": "mcu" },
{ "name": "led_2", "plugin": "LED", "library": "mcu" },
{ "name": "led_3", "plugin": "LED", "library": "mcu" },
{ "name": "led_4", "plugin": "LED", "library": "mcu" },
{ "name": "led_5", "plugin": "LED", "library": "mcu" },
{ "name": "led_6", "plugin": "LED", "library": "mcu" },
{ "name": "led_7", "plugin": "LED", "library": "mcu" },
{ "name": "led_8", "plugin": "LED", "library": "mcu" },
{ "name": "led_9", "plugin": "LED", "library": "mcu" },
{ "name": "led_10", "plugin": "LED", "library": "mcu" },
{ "name": "led_11", "plugin": "LED", "library": "mcu" },
{ "name": "led_12", "plugin": "LED", "library": "mcu" },
{ "name": "led_13", "plugin": "LED", "library": "mcu" },
{ "name": "led_14", "plugin": "LED", "library": "mcu" },
{ "name": "led_15", "plugin": "LED", "library": "mcu" }
],
// Conexiunea pluginului între componente
"connections": [
[ "u1_stm32.ports.usart1_m", "usart_debug.ports.term_s"],
[ "u1_stm32.ports.usart1_s", "usart_debug.ports.term_m"],
[ "u1_stm32.ports.usart2_m", "bluetooth.ports.usart_m"],
[ "u1_stm32.ports.usart2_s", "bluetooth.ports.usart_s"],
[ "bluetooth.ports.bt_s", "term_bt.ports.term_m"],
[ "bluetooth.ports.bt_m", "term_bt.ports.term_s"],
[ "led_0.ports.pin", "u1_stm32.buses.pin_output_a", "0x00"],
[ "led_1.ports.pin", "u1_stm32.buses.pin_output_a", "0x01"],
[ "led_2.ports.pin", "u1_stm32.buses.pin_output_a", "0x02"],
[ "led_3.ports.pin", "u1_stm32.buses.pin_output_a", "0x03"],
[ "led_4.ports.pin", "u1_stm32.buses.pin_output_a", "0x04"],
[ "led_5.ports.pin", "u1_stm32.buses.pin_output_a", "0x05"],
[ "led_6.ports.pin", "u1_stm32.buses.pin_output_a", "0x06"],
[ "led_7.ports.pin", "u1_stm32.buses.pin_output_a", "0x07"],
[ "led_8.ports.pin", "u1_stm32.buses.pin_output_a", "0x08"],
[ "led_9.ports.pin", "u1_stm32.buses.pin_output_a", "0x09"],
[ "led_10.ports.pin", "u1_stm32.buses.pin_output_a", "0x0A"],
[ "led_11.ports.pin", "u1_stm32.buses.pin_output_a", "0x0B"],
[ "led_12.ports.pin", "u1_stm32.buses.pin_output_a", "0x0C"],
[ "led_13.ports.pin", "u1_stm32.buses.pin_output_a", "0x0D"],
[ "led_14.ports.pin", "u1_stm32.buses.pin_output_a", "0x0E"],
[ "led_15.ports.pin", "u1_stm32.buses.pin_output_a", "0x0F"]
]
}Atenție la parametrul firmware în secțiunea params — este numele fișierului care poate fi încărcat pe un dispozitiv virtual ca firmware.
Dispozitivul virtual și interacțiunea acestuia cu sistemul de operare principal pot fi reprezentate prin următorul schematic:

Exemplarul curent al emulatorului implică interacțiunea cu porturile COM ale sistemului de operare principal (UART-ul de depanare și UART-ul pentru modulul Bluetooth). Acestea pot fi porturi reale, la care sunt conectate dispozitive sau porturi COM virtuale (pentru care este nevoie de com0com / socat).
Pentru a interacționa cu emulatorul din exterior, în prezent există două modalități principale:
- protocolul GDB RSP (respectiv, uneltele care suportă acest protocol — Eclipse / IDA / radare2);
- linia de comandă internă a emulatorului (Argparse sau Python).
Porturi COM virtuale
Pentru a interacționa cu UART-ul dispozitivului virtual pe o mașină locală prin terminal, este necesară crearea unei perechi de porturi COM virtuale legate. În cazul nostru, un port este utilizat de emulator, iar celălalt — de programul-terminal (PuTTY sau screen):

Utilizarea com0com
Porturile COM virtuale sunt configurate cu ajutorul utilitarului setup din pachetul com0com (versiunea de consolă — C:Program Files (x86)com0comsetupс.exe, sau versiunea GUI — C:Program Files (x86)com0comsetupg.exe):

Trebuie să bifezi opțiunile enable buffer overrun pentru toate porturile virtuale create, altfel emulatorul va aștepta un răspuns de la portul COM.
Utilizarea socat
Pe sistemele UNIX, porturile COM virtuale sunt create automat de emulator cu ajutorul utilitarului socat; pentru aceasta, este suficient să specifici prefixul socat:.
Interfața internă a liniei de comandă (Argparse sau Python)
Dat fiind că Kopycat este o aplicație de consolă, pentru interacțiunea cu obiectele și variabilele sale, emulatorul oferă două opțiuni de interfață pentru linia de comandă: Argparse și Python.
Argparse — este CLI-ul integrat în Kopycat, disponibil întotdeauna și tuturor.
CLI-ul alternativ — interpretorul Python. Pentru utilizarea acestuia, este necesară instalarea modulului Python Jep și configurarea emulatorului pentru a lucra cu Python (va fi utilizat interpretorul Python instalat în sistemul principal al utilizatorului).
Instalarea modulului Python Jep
Pe Linux, Jep poate fi instalat prin pip:
pip install jepPentru instalarea Jep pe Windows este necesară instalarea prealabilă a Windows SDK și a Microsoft Visual Studio corespunzătoare. Am simplificat puțin sarcina și am realizat JEP pentru versiunile actuale de Python pe Windows, astfel că modulul poate fi instalat din fișier:
pip install jep-3.8.2-cp27-cp27m-win_amd64.whlPentru a verifica instalarea Jep, trebuie să executați în linia de comandă:
python -c "import jep"Răspunsul ar trebui să fie mesajul:
ImportError: Jep nu este suportat în Python autonom, trebuie să fie încapsulat în Java.În fișierul de comandă al emulatorului pentru sistemul dumneavoastră (kopycat.bat — pentru Windows, kopycat — pentru Linux) la lista de parametri DEFAULT_JVM_OPTS adăugați un parametru suplimentar Djava.library.path — acesta trebuie să conțină calea către modulul Jep instalat.
În rezultat, pentru Windows ar trebui să obțineți un format de linie similar cu următorul:
set DEFAULT_JVM_OPTS="-XX:MaxMetaspaceSize=256m" "-XX:+UseParallelGC" "-XX:SurvivorRatio=6" "-XX:-UseGCOverheadLimit" "-Djava.library.path=C:/Python27/Lib/site-packages/jep"Pornirea Kopycat
Emulatorul este o aplicație JVM de tip consolă. Pornirea se realizează printr-un script de linie de comandă al sistemului de operare (sh/cmd).
Comanda pentru pornirea pe Windows:
binkopycat -g 23946 -n rhino -l user -y library -p firmware=firmwarerhino_pass.bin,tty_dbg=COM26,tty_bt=COM28Comanda pentru pornirea pe Linux utilizând utilitarul socat:
./bin/kopycat -g 23946 -n rhino -l user -y library -p firmware=./firmware/rhino_pass.bin, tty_dbg=socat:./COM26,tty_bt=socat:./COM28-g 23646— TCP-portul care va fi deschis pentru acces la serverul GDB;-n rhino— numele modulului principal al sistemului (dispozitivul asamblat);-l user— numele bibliotecii pentru căutarea modulului principal;-y library— calea pentru a căuta modulele care fac parte din dispozitiv;firmwarerhino_pass.bin— calea către fișierul de firmware;- COM26 și COM28 — porturi COM virtuale.
În rezultat, va fi afișat un prompt Python > (sau Argparse >):
18:07:59 INFO [eFactoryBuilder.create]: Modulul top a fost creat cu succes ca top
18:07:59 INFO [Module.initializeAndRes]: Configurarea nucleului pentru top.u1_stm32.cortexm0.arm pentru top
18:07:59 INFO [Module.initializeAndRes]: Configurarea debuggerului pentru top.u1_stm32.dbg pentru top
18:07:59 WARN [Module.initializeAndRes]: Tracer nu a fost găsit în top...
18:07:59 INFO [Module.initializeAndRes]: Inițializarea porturilor și busurilor...
18:07:59 WARN [Module.initializePortsA]: ATENȚIE: Unele porturi au avertizări, utilizați printModulesPortsWarnings pentru a le vedea...
18:07:59 FINE [ARMv6CPU.reset]: Setarea adresei punctului de intrare la 08006A75
18:07:59 INFO [Module.initializeAndRes]: Modulul top este inițializat și resetat cu succes ca celulă principală!
18:07:59 INFO [Kopycat.open]: Începerea virtualizării plăcii top[rhino] cu arm[ARMv6Core]
18:07:59 INFO [GDBServer.debuggerModule]: Setarea unui nou modul de debugger top.u1_stm32.dbg pentru GDB_SERVER(port=23946,alive=true)
Python >Interacțiunea cu IDA Pro
Ca fișier sursă pentru analiză în IDA, pentru a facilita testarea, utilizăm firmware-ul „Rhinoceros” sub formă de (acolo este salvată meta-informația).
De asemenea, puteți utiliza firmware-ul principal fără meta-informație.
După pornirea Kopycat în IDA Pro, în meniul Debugger, mergem la opțiunea „Switch debugger…” și alegem „Depurator GDB la distanță«. Configurăm conexiunea: meniul Depurator — Opțiuni pentru proces…
Setăm valorile:
- Aplicație — orice valoare
- Hostname: 127.0.0.1 (sau adresa IP a mașinii de la distanță, unde este pornit Kopycat)
- Port: 23946

Acum devine disponibil butonul de pornire a depurării (tasta F9):
![]()
Apăsăm acest buton — se realizează conexiunea cu modul depurator în emulator. IDA trece în modul de depurare, devin disponibile feronine suplimentare: informații despre registre, despre stivă.
Acum putem folosi toate funcționalitățile standard ale depuratorului:
- execuția pas cu pas a instrucțiunilor (Intrați în și Săriți peste — tastele F7 și F8, respectiv);
- pornirea și suspendarea execuției;
- crearea punctelor de oprire atât pentru cod, cât și pentru date (tasta F2).
Conectarea la depurator nu înseamnă pornirea codului firmware-ului. Poziția curentă pentru execuție trebuie să fie adresa 0x08006A74 — începutul funcției Reset_Handler. Dacă derulăm lista mai jos, putem vedea apelul funcției main. Putem plasa cursorul pe această linie (adresa 0x08006ABE) și executa operația Executare până la cursor (tasta F4).

Apoi putem apăsa F7 pentru a intra în funcție main.
Dacă executăm comanda Continuare proces (tasta F9), va apărea o fereastră «Vă rugăm să așteptați» cu un singur buton Suspendare:

La apăsarea acesteia, Suspendare execuția codului firmware-ului este suspendată și poate fi reluată de la aceeași adresă în cod, unde a fost oprită.
Dacă continuăm execuția codului, atunci în terminalele conectate la porturi COM virtuale pot apărea următoarele linii:


Prezența liniei «state bypass» indică faptul că modulul Bluetooth virtual a trecut în modul de receptare a datelor de la portul COM al utilizatorului.
Acum în terminalul Bluetooth (în imagine — COM29) putem introduce comenzi conform protocolului «Rhinoceros». De exemplu, în urma comenzii «MEOW», terminalul Bluetooth va returna linia «mur-mur»:

Emulează-mă complet
Când construim emulatorul, putem alege gradul de detaliere/emulare al unui anumit dispozitiv. De exemplu, modulul Bluetooth poate fi emulat în diferite moduri:
- se emulează complet dispozitivul cu un set complet de comenzi;
- se emulează comenzi AT, iar fluxul de date este primit de la portul COM al sistemului principal;
- dispozitivul virtual asigură redirecționarea completă a datelor către dispozitivul real;
- sub formă de o simplă măsură care întotdeauna returnează „OK”.
În versiunea curentă a emulatorului, este folosită a doua abordare – un modul Bluetooth virtual configurează, după care trece în modul de „proxy” pentru datele din portul COM al sistemului principal în portul UART al emulatorului.

Să examinăm posibilitatea unei instrumentări simple a codului în cazul în care nu este implementată vreo parte a periferiei. De exemplu, dacă nu este creat un timer care să se ocupe de controlul transmiterii datelor în DMA (verificarea se face în funcția ws2812b_wait, situată la adresa 0x08006840), firmware-ul va aștepta întotdeauna resetarea flagului busy, situat la adresa 0x200004C4, care arată ocuparea liniei de date DMA:

Putem evita această situație prin „resetarea manuală” a flagului busy imediat după ce a fost setat. În IDA Pro putem crea o funcție Python și o putem apela în breakpoint, punând break point-ul în cod după ce am scris valoarea 1 în flag. busy.
Breakpoint-Handler
Mai întâi, să creăm o funcție Python în IDA. Meniul File – Script command...
Adăugăm un nou snippet în lista din stânga, îi dăm un nume (de exemplu, BPT),
în câmpul de text din dreapta introducem codul funcției:
def skip_dma():
print "Skipping wait ws2812..."
value = Byte(0x200004C4)
if value == 1:
PatchDbgByte(0x200004C4, 0)
return False 
După aceasta, facem click pe Run și închidem fereastra scripturilor.
Acum să ne îndreptăm către codul de la adresa 0x0800688A, setăm breakpoint (tasta F2), îi edităm (meniul contextual Edit breakpoint...), nu uitați să setați tipul scriptului – Python:


Dacă valoarea curentă a flagului busy este egală cu 1, atunci trebuie să executăm funcția skip_dma în linia scripturilor:

Dacă pornim firmware-ul pentru execuție, activarea codului breakpoint-handler-ului poate fi observată în IDA în fereastra Registrul direcției, registrul de ieșire. În acest proiect, nu ne vor fi necesare. la linia Skipping wait ws2812.... Acum firmware-ul nu va aștepta resetarea flagului. busy.
Interacțiunea cu emulatorul
Emularea doar de dragul emulării nu va provoca entuziasm sau bucurie. Mult mai interesant este dacă emulatorul ajută cercetătorul să vizualizeze datele din memorie sau să stabilească interacțiunea firelor.
Să arătăm cum se poate stabili interacțiunea RTOS-tasks în dinamică. Este necesar să suspendăm executarea codului, dacă este deja pornit. Dacă ne îndreptăm spre funcția bluetooth_task_entry în ramura de gestionare a comenzii „LED” (adresa 0x080057B8), putem observa că mai întâi se creează și apoi se trimite într-o coadă de sistem ledControlQueueHandle un anumit mesaj.

Este necesar să stabilim un breakpoint la accesarea variabilei ledControlQueueHandle, situată la adresa 0x20000624 și să continuăm executarea codului:

Ca urmare, întâi va avea loc o oprire la adresa 0x080057CA înainte de apelarea funcției osMailAlloc, apoi — la adresa 0x08005806 înainte de apelarea funcției osMailPut, apoi, după un timp — la adresa 0x08005BD4 (înainte de apelarea funcției osMailGet), care aparține funcției leds_task_entry (task LED), adică a avut loc o schimbare a task-urilor, iar acum controlul a fost preluat de task-ul LED.

În acest mod simplu se poate stabili cum interacționează task-urile RTOS între ele.
Desigur, în realitate, interacțiunea task-urilor poate fi mai complexă, dar utilizând emulatorul, urmărirea acestei interacțiuni devine mai puțin laborios.
se poate viziona un scurt videoclip despre lansarea emulatorului și interacțiunea cu IDA Pro.
Lansare cu Radare2
Nu se poate să nu menționăm un astfel de instrument versatil ca Radare2.
Pentru a te conecta la emulator folosind r2, comanda va arăta astfel:
radare2 -A -a arm -b 16 -d gdb://localhost:23946 rhino_fw42k6.elfAcum sunt disponibile lansarea (dc) și suspendarea execuției (Ctrl+C).
Din păcate, în prezent, în r2 există probleme la lucrul cu serverul gdb hardware și cu maparea memoriei, din această cauză nu funcționează punctele de oprire și pașii (comanda ds). Sperăm că, în curând, acest lucru va fi rezolvat.
Lansare cu Eclipse
Una dintre variantele de utilizare a emulatorului este depanarea firmware-ului dispozitivului în dezvoltare. Pentru a fi mai clar, vom folosi de asemenea firmware-ul „Rhinoceros”. Poți descărca sursele firmware-ului .
Ca IDE, vom folosi Eclipse din pachetul .
Pentru ca firmware-ul să fie încărcat în emulator direct din Eclipse, trebuie adăugat parametrul firmware=null în comanda de lansare a emulatorului:
binkopycat -g 23946 -n rhino -l user -y modules -p firmware=null,tty_dbg=COM26,tty_bt=COM28Configurarea configurației debug
În Eclipse, alegem meniul Run — Debug Configurations… În fereastra deschisă, în secțiunea GDB Hardware Debugging trebuie să adăugăm o nouă configurație, după care, pe tab-ul „Main”, să indicăm proiectul curent și aplicația pentru depanare:

Pe tab-ul „Debugger” trebuie să indicăm comanda GDB:
${openstm32_compiler_path}arm-none-eabi-gdb
De asemenea, trebuie introduse parametrii pentru conectarea la serverul GDB (host și port):

Pe tab-ul „Startup” trebuie să indicăm următorii parametrii:
- să bifezi căsuța Load image (pentru a permite încărcarea în emulator a imaginii firmware-ului compilate);
- să bifezi căsuța Load symbols;
- să adaugăm comanda de lansare:
set $pc = *0x08000004(afisează în registrul PC valoarea din memorie la adresa0x08000004— acolo este stocat adresa ResetHandler-ului).
Vă rugăm să observați, dacă nu doriți să încărcați fișierul de firmware din Eclipse, atunci parametrii Load image și Comenzi de rulare nu trebuie specificate.

După apăsarea pe Debug, puteți lucra în modul de depanare:
- execuția pas cu pas a codului

- interacțiunea cu punctele de oprire

Notă. În Eclipse există, hmmm... unele particularități... și trebuie să trăim cu ele. De exemplu, dacă la pornirea debugger-ului apare mesajul „No source available for «0x0»”, atunci executați comanda Step (F5)

În concluzie
Emularea codului nativ este o activitate destul de interesantă. Pentru dezvoltatorul de dispozitive, oferă posibilitatea de a debuga firmware-ul fără dispozitivul real. Pentru cercetător — oportunitatea de a efectua analiza dinamică a codului, ceea ce nu este întotdeauna posibil chiar și în prezența unui dispozitiv.
Dorim să oferim specialiștilor un instrument care să fie convenabil, în mod rezonabil simplu și să nu consume mult timp și efort pentru configurare și pornire.
Scrieți în comentarii despre experiența dumneavoastră cu emulatorii hardware. Vă invităm la discuții și vom fi bucuroși să răspundem la întrebări.
Numai utilizatorii înregistrați pot participa la sondaj. , vă rugăm.
Pentru ce folosiți emulatorul?
dezvolt firmware (depanare)
cercetez firmware
lansez jocuri (Dendi, Sega, PSP)
altceva (scrieți în comentarii)
7 utilizatori au votat. 2 utilizatori s-au abținut.
Ce software folosiți pentru emularea codului nativ?
QEMU
Unicorn engine
Proteus
altceva (scrieți în comentarii)
6 utilizatori au votat. 2 utilizatori s-au abținut.
Ce ați dori să îmbunătățiți în emulatorul pe care îl folosiți?
aș dori viteză
aș dori să fie mai ușor de configurat/înlansat
aș dori mai multe funcționalități de interacțiune cu emulatorul (API, hook-uri)
sunt mulțumit de tot
altceva (scrieți în comentarii)
8 utilizatori au votat. 1 utilizator s-a abținut.
Sursa: habr.com


