V rámci stretnutia 0x0A DC7831
V tomto článku popíšeme, ako spustiť firmvér zariadenia v emulátore, demonštrovať interakciu s debuggerom a vykonať malú dynamickú analýzu firmvéru.
pravek
Kedysi dávno v ďalekej galaxii
Pred niekoľkými rokmi bolo v našom laboratóriu potrebné preskúmať firmvér zariadenia. Firmvér bol komprimovaný a rozbalený pomocou zavádzača. Urobil to veľmi komplikovaným spôsobom, pričom údaje v pamäti niekoľkokrát posunul. A samotný firmvér potom aktívne interagoval s perifériami. A to všetko na jadre MIPS.
Dostupné emulátory nám z objektívnych príčin nevyhovovali, no aj tak sme chceli spustiť kód. Potom sme sa rozhodli vyrobiť si vlastný emulátor, ktorý by urobil minimum a umožnil nám rozbaliť hlavný firmvér. Skúsili sme to a vyšlo to. Rozmýšľali sme, čo keby sme pridali periférie, ktoré by vykonávali aj hlavný firmvér. Veľmi to nebolelo - a tiež to vyšlo. Znova sme sa zamysleli a rozhodli sme sa urobiť plnohodnotný emulátor.
Výsledkom bol emulátor počítačových systémov
Prečo práve Kopycat?
Je tam slovná hračka.
- imitátor (English, noun [ˈkɒpɪkæt]) - imitátor, imitátor
- ako (anglicky, podstatné meno [ˈkæt]) - mačka, mačka - obľúbené zviera jedného z tvorcov projektu
- Písmeno „K“ pochádza z programovacieho jazyka Kotlin
Kopírka
Pri vytváraní emulátora boli stanovené veľmi špecifické ciele:
- schopnosť rýchlo vytvárať nové periférie, moduly, jadrá procesorov;
- schopnosť zostaviť virtuálne zariadenie z rôznych modulov;
- možnosť načítať akékoľvek binárne dáta (firmvér) do pamäte virtuálneho zariadenia;
- schopnosť pracovať so snímkami (snímky stavu systému);
- schopnosť komunikovať s emulátorom prostredníctvom vstavaného debuggera;
- pekný moderný jazyk pre rozvoj.
V dôsledku toho bol na implementáciu vybraný Kotlin, architektúra zbernice (to je, keď moduly komunikujú medzi sebou cez virtuálne dátové zbernice), JSON ako formát popisu zariadenia a GDB RSP ako protokol pre interakciu s debuggerom.
Vývoj prebieha už niečo vyše dvoch rokov a aktívne pokračuje. Počas tejto doby boli implementované procesorové jadrá MIPS, x86, V850ES, ARM a PowerPC.
Projekt sa rozrastá a je načase predstaviť ho širšej verejnosti. Podrobný popis projektu urobíme neskôr, ale zatiaľ sa zameriame na používanie Kopycatu.
Pre tých najnetrpezlivejších je možné stiahnuť si promo verziu emulátora
Rhino v emulátore
Pripomeňme, že už skôr pre konferenciu SMARTRHINO-2018 bolo vytvorené testovacie zariadenie „Rhinoceros“ na výučbu zručností reverzného inžinierstva. Proces analýzy statického firmvéru bol opísaný v
Teraz sa pokúsme pridať „reproduktory“ a spustiť firmvér v emulátore.
potrebujeme:
1) Java 1.8
2) Python a modul
Pre Windows:
1)
2)
Pre Linux:
1) socat
Ako klienta GDB môžete použiť Eclipse, IDA Pro alebo radare2.
Ako to funguje?
Na vykonanie firmvéru v emulátore je potrebné „zostaviť“ virtuálne zariadenie, ktoré je analógom skutočného zariadenia.
Skutočné zariadenie („nosorožec“) môže byť znázornené na blokovej schéme:
Emulátor má modulárnu štruktúru a konečné virtuálne zariadenie môže byť opísané v súbore JSON.
JSON 105 riadkov
{
"top": true,
// Plugin name should be the same as file name (or full path from library start)
"plugin": "rhino",
// Directory where plugin places
"library": "user",
// Plugin parameters (constructor parameters if jar-plugin version)
"params": [
{ "name": "tty_dbg", "type": "String"},
{ "name": "tty_bt", "type": "String"},
{ "name": "firmware", "type": "String", "default": "NUL"}
],
// Plugin outer ports
"ports": [ ],
// Plugin internal buses
"buses": [
{ "name": "mem", "size": "BUS30" },
{ "name": "nand", "size": "4" },
{ "name": "gpio", "size": "BUS32" }
],
// Plugin internal components
"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" }
],
// Plugin connection between components
"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"]
]
}
Venujte pozornosť parametrom firmware časť params je názov súboru, ktorý je možné nahrať do virtuálneho zariadenia ako firmvér.
Virtuálne zariadenie a jeho interakcia s hlavným operačným systémom môže byť znázornená nasledujúcim diagramom:
Aktuálna testovacia inštancia emulátora zahŕňa interakciu s COM portami hlavného OS (ladenie UART a UART pre modul Bluetooth). Môžu to byť skutočné porty, ku ktorým sú pripojené zariadenia, alebo virtuálne porty COM (na to potrebujete com0com/socat).
V súčasnosti existujú dva hlavné spôsoby interakcie s emulátorom zvonku:
- protokol GDB RSP (podľa toho nástroje, ktoré podporujú tento protokol, sú Eclipse / IDA / radare2);
- príkazový riadok interného emulátora (Argparse alebo Python).
Virtuálne COM porty
Aby ste mohli interagovať s UART virtuálneho zariadenia na lokálnom počítači cez terminál, musíte vytvoriť pár priradených virtuálnych COM portov. V našom prípade jeden port používa emulátor a druhý terminálový program (PuTTY alebo obrazovka):
Pomocou com0com
Virtuálne COM porty sa konfigurujú pomocou nastavovacej pomôcky zo súpravy com0com (verzia konzoly - C: Program Files (x86) com0comsetupс.exe, alebo verzia GUI - C: Program Files (x86) com0comsetupg.exe):
Začiarknite políčka povoliť pretečenie vyrovnávacej pamäte pre všetky vytvorené virtuálne porty, inak bude emulátor čakať na odpoveď z COM portu.
Pomocou socat
Na systémoch UNIX sú virtuálne COM porty automaticky vytvorené emulátorom pomocou nástroja socat; na to stačí zadať predponu v názve portu pri spustení emulátora socat:
.
Interné rozhranie príkazového riadka (Argparse alebo Python)
Keďže Kopycat je konzolová aplikácia, emulátor poskytuje dve možnosti rozhrania príkazového riadka na interakciu s jeho objektmi a premennými: Argparse a Python.
Argparse je CLI zabudovaný do Kopycat a je vždy dostupný pre každého.
Alternatívnym CLI je interpret Python. Ak ho chcete použiť, musíte nainštalovať modul Jep Python a nakonfigurovať emulátor na prácu s Pythonom (použije sa interpret Python nainštalovaný v hlavnom systéme používateľa).
Inštalácia modulu Python Jep
Pod Linuxom je možné Jep nainštalovať cez pip:
pip install jep
Ak chcete nainštalovať Jep na Windows, musíte najprv nainštalovať Windows SDK a príslušné Microsoft Visual Studio. Trochu sme vám to uľahčili a
pip install jep-3.8.2-cp27-cp27m-win_amd64.whl
Ak chcete skontrolovať inštaláciu Jep, musíte spustiť príkazový riadok:
python -c "import jep"
Ako odpoveď by ste mali dostať nasledujúcu správu:
ImportError: Jep is not supported in standalone Python, it must be embedded in Java.
V dávkovom súbore emulátora pre váš systém (copycat.bat - pre Windows, kopírovať - pre Linux) do zoznamu parametrov DEFAULT_JVM_OPTS
pridať ďalší parameter Djava.library.path
— musí obsahovať cestu k nainštalovanému modulu Jep.
Výsledkom pre Windows by mal byť takýto riadok:
set DEFAULT_JVM_OPTS="-XX:MaxMetaspaceSize=256m" "-XX:+UseParallelGC" "-XX:SurvivorRatio=6" "-XX:-UseGCOverheadLimit" "-Djava.library.path=C:/Python27/Lib/site-packages/jep"
Spustenie Kopycatu
Emulátorom je konzolová aplikácia JVM. Spustenie sa vykonáva pomocou skriptu príkazového riadka operačného systému (sh/cmd).
Príkaz na spustenie v systéme Windows:
binkopycat -g 23946 -n rhino -l user -y library -p firmware=firmwarerhino_pass.bin,tty_dbg=COM26,tty_bt=COM28
Príkaz na spustenie v systéme Linux pomocou nástroja 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 port, ktorý bude otvorený pre prístup k serveru GDB;-n rhino
— názov hlavného systémového modulu (zmontovaného zariadenia);-l user
— názov knižnice na vyhľadanie hlavného modulu;-y library
— cesta na vyhľadávanie modulov obsiahnutých v zariadení;firmwarerhino_pass.bin
— cesta k súboru firmvéru;- COM26 a COM28 sú virtuálne COM porty.
V dôsledku toho sa zobrazí výzva Python >
(Alebo Argparse >
):
18:07:59 INFO [eFactoryBuilder.create ]: Module top successfully created as top
18:07:59 INFO [ Module.initializeAndRes]: Setup core to top.u1_stm32.cortexm0.arm for top
18:07:59 INFO [ Module.initializeAndRes]: Setup debugger to top.u1_stm32.dbg for top
18:07:59 WARN [ Module.initializeAndRes]: Tracer wasn't found in top...
18:07:59 INFO [ Module.initializeAndRes]: Initializing ports and buses...
18:07:59 WARN [ Module.initializePortsA]: ATTENTION: Some ports has warning use printModulesPortsWarnings to see it...
18:07:59 FINE [ ARMv6CPU.reset ]: Set entry point address to 08006A75
18:07:59 INFO [ Module.initializeAndRes]: Module top is successfully initialized and reset as a top cell!
18:07:59 INFO [ Kopycat.open ]: Starting virtualization of board top[rhino] with arm[ARMv6Core]
18:07:59 INFO [ GDBServer.debuggerModule ]: Set new debugger module top.u1_stm32.dbg for GDB_SERVER(port=23946,alive=true)
Python >
Interakcia s IDA Pro
Pre zjednodušenie testovania používame ako zdrojový súbor pre analýzu v IDA vo formulári firmvér Rhina
Môžete tiež použiť hlavný firmvér bez meta informácií.
Po spustení Kopycat v IDA Pro prejdite v ponuke Debugger na položku „Prepnúť ladiaci program…"a vyberte"Vzdialený ladiaci program GDB". Ďalej nastavte pripojenie: menu Ladiaci nástroj – možnosti spracovania…
Nastavte hodnoty:
- Aplikácia - akákoľvek hodnota
- Názov hostiteľa: 127.0.0.1 (alebo adresa IP vzdialeného počítača, na ktorom je spustený Kopycat)
- Port: 23946
Teraz je k dispozícii tlačidlo ladenia (kláves F9):
Kliknutím naň sa pripojíte k modulu ladiaceho nástroja v emulátore. IDA prejde do režimu ladenia, sprístupnia sa ďalšie okná: informácie o registroch, o zásobníku.
Teraz môžeme použiť všetky štandardné funkcie debuggera:
- vykonávanie pokynov krok za krokom (Vojsť и Prekročiť — klávesy F7 a F8;
- spustenie a pozastavenie vykonávania;
- vytváranie bodov prerušenia pre kód aj údaje (kláves F2).
Pripojenie k debuggeru neznamená spustenie kódu firmvéru. Aktuálna pozícia vykonávania musí byť adresa 0x08006A74
— začiatok funkcie Reset_Handler. Ak rolujete nadol v zozname, môžete vidieť volanie funkcie hlavné. Na tento riadok môžete umiestniť kurzor (adresa 0x08006ABE
) a vykonajte operáciu Spustiť, kým kurzor (kláves F4).
Potom môžete stlačením klávesu F7 vstúpiť do funkcie hlavné.
Ak spustíte príkaz Pokračujte v procese (kláves F9), potom sa zobrazí okno „Čakajte prosím“ s jediným tlačidlom Pozastaviť:
Keď stlačíte Pozastaviť vykonávanie kódu firmvéru je pozastavené a môže pokračovať z rovnakej adresy v kóde, kde bolo prerušené.
Ak budete pokračovať vo vykonávaní kódu, v termináloch pripojených k virtuálnym portom COM uvidíte nasledujúce riadky:
Prítomnosť riadku „vynechanie stavu“ znamená, že virtuálny modul Bluetooth sa prepol do režimu prijímania údajov z COM portu používateľa.
Teraz v termináli Bluetooth (COM29 na obrázku) môžete zadávať príkazy v súlade s protokolom Rhino. Napríklad príkaz „MEOW“ vráti reťazec „mur-mur“ do terminálu Bluetooth:
Napodobniť ma nie úplne
Pri zostavovaní emulátora si môžete zvoliť úroveň detailov/emulácie konkrétneho zariadenia. Napríklad modul Bluetooth je možné emulovať rôznymi spôsobmi:
- zariadenie je plne emulované s úplnou sadou príkazov;
- AT príkazy sú emulované a dátový tok je prijímaný z COM portu hlavného systému;
- virtuálne zariadenie poskytuje úplné presmerovanie údajov do skutočného zariadenia;
- ako jednoduchý stub, ktorý vždy vráti "OK".
Aktuálna verzia emulátora využíva druhý prístup - virtuálny modul Bluetooth vykoná konfiguráciu, po ktorej sa prepne do režimu „proxy“ dát z COM portu hlavného systému na UART port emulátora.
Uvažujme o možnosti jednoduchej inštrumentácie kódu v prípade, že niektorá časť periférie nie je implementovaná. Napríklad, ak nebol vytvorený časovač zodpovedný za riadenie prenosu dát do DMA (kontrola sa vykonáva vo funkcii ws2812b_waitumiestnený na 0x08006840
), potom bude firmvér vždy čakať na resetovanie príznaku zaneprázdnenýumiestnený na 0x200004C4
ktorý ukazuje obsadenosť dátovej linky DMA:
Túto situáciu môžeme obísť manuálnym resetovaním vlajky zaneprázdnený ihneď po jeho inštalácii. V IDA Pro môžete vytvoriť funkciu Pythonu a zavolať ju v bode prerušenia a samotný bod prerušenia vložiť do kódu po zapísaní hodnoty 1 do príznaku zaneprázdnený.
Obsluha bodu zlomu
Najprv si v IDA vytvoríme funkciu Pythonu. Ponuka Súbor - príkaz skriptu...
Pridajte nový úryvok do zoznamu vľavo, pomenujte ho (napr. BPT),
Do textového poľa napravo zadajte kód funkcie:
def skip_dma():
print "Skipping wait ws2812..."
value = Byte(0x200004C4)
if value == 1:
PatchDbgByte(0x200004C4, 0)
return False
Potom kliknite beh a zatvorte okno skriptu.
Teraz poďme ku kódu na adrese 0x0800688A
, nastavte bod prerušenia (kláves F2), upravte ho (kontextové menu Upraviť bod prerušenia...), nezabudnite nastaviť typ skriptu na Python:
Ak je aktuálna hodnota príznaku zaneprázdnený rovná sa 1, potom by ste mali funkciu spustiť skip_dma v riadku skriptu:
Ak spustíte firmvér na spustenie, spustenie kódu obsluhy bodu prerušenia môžete vidieť v okne IDA Výkon riadkom Skipping wait ws2812...
. Firmvér teraz nebude čakať na resetovanie príznaku zaneprázdnený.
Interakcia s emulátorom
Emulácia kvôli emulácii pravdepodobne nespôsobí potešenie a radosť. Je oveľa zaujímavejšie, ak emulátor pomáha výskumníkovi vidieť údaje v pamäti alebo vytvoriť interakciu vlákien.
Ukážeme vám, ako dynamicky nadviazať interakciu medzi úlohami RTOS. Najprv by ste mali pozastaviť vykonávanie kódu, ak je spustený. Ak prejdete na funkciu bluetooth_task_entry do vetvy spracovania príkazu „LED“ (adresa 0x080057B8
), potom môžete vidieť, čo sa najskôr vytvorí a potom odošle do systémového frontu ledControlQueueHandle nejakú správu.
Na prístup k premennej by ste mali nastaviť bod prerušenia ledControlQueueHandleumiestnený na 0x20000624
a pokračujte vo vykonávaní kódu:
Výsledkom je, že k zastaveniu dôjde najskôr na adrese 0x080057CA
pred volaním funkcie osMailAlloc, potom na adrese 0x08005806
pred volaním funkcie osMailPut, potom po chvíli - na adresu 0x08005BD4
(pred volaním funkcie osMailGet), ktorá patrí do funkcie leds_task_entry (LED-task), teda úlohy sa prepnuli a teraz LED-task dostala kontrolu.
Týmto jednoduchým spôsobom môžete určiť, ako sa úlohy RTOS navzájom ovplyvňujú.
Samozrejme, v skutočnosti môže byť interakcia úloh komplikovanejšia, ale pomocou emulátora je sledovanie tejto interakcie menej pracné.
Spustite pomocou Radare2
Nemôžete ignorovať taký univerzálny nástroj, akým je Radare2.
Ak sa chcete pripojiť k emulátoru pomocou r2, príkaz by vyzeral takto:
radare2 -A -a arm -b 16 -d gdb://localhost:23946 rhino_fw42k6.elf
Spustenie je k dispozícii hneď (dc
) a pozastaviť vykonávanie (Ctrl+C).
Bohužiaľ, v súčasnosti má r2 problémy pri práci s hardvérovým serverom gdb a rozložením pamäte; z tohto dôvodu nefungujú body prerušenia a kroky (príkaz ds
). Dúfame, že to bude čoskoro opravené.
Beh s Eclipse
Jednou z možností použitia emulátora je odladenie firmvéru vyvíjaného zariadenia. Pre prehľadnosť použijeme aj firmvér Rhina. Môžete si stiahnuť zdroje firmvéru
Ako IDE použijeme Eclipse zo sady
Aby emulátor načítal firmvér priamo skompilovaný v Eclipse, musíte pridať parameter firmware=null
na príkaz na spustenie emulátora:
binkopycat -g 23946 -n rhino -l user -y modules -p firmware=null,tty_dbg=COM26,tty_bt=COM28
Nastavenie konfigurácie ladenia
V Eclipse vyberte ponuku Spustiť - ladiť konfigurácie... V okne, ktoré sa otvorí, v sekcii Hardvérové ladenie GDB musíte pridať novú konfiguráciu a potom na karte „Hlavné“ určiť aktuálny projekt a aplikáciu na ladenie:
Na karte „Debugger“ musíte zadať príkaz GDB:
${openstm32_compiler_path}arm-none-eabi-gdb
A tiež zadajte parametre na pripojenie k serveru GDB (hostiteľ a port):
Na karte „Spustenie“ musíte zadať nasledujúce parametre:
- povoliť začiarkavacie políčko Načítať obrázok (takže zostavený obraz firmvéru sa načíta do emulátora);
- povoliť začiarkavacie políčko Symboly zaťaženia;
- pridať príkaz na spustenie:
set $pc = *0x08000004
(nastavte register PC na hodnotu z pamäte na adrese0x08000004
- tam je uložená adresa ResetHandler).
Venujte pozornosť, ak nechcete stiahnuť súbor firmvéru z Eclipse, potom možnosti Načítať obrázok и Spustite príkazy netreba uvádzať.
Po kliknutí na tlačidlo Debug môžete pracovať v režime ladenia:
- krok za krokom spúšťanie kódu
- interakcie s bodmi prerušenia
Poznámka. Eclipse má, hmm... nejaké vrtochy... a musíte s nimi žiť. Napríklad, ak sa pri spustení ladiaceho programu zobrazí správa „No source available for „0x0““, potom vykonajte príkaz Krok (F5)
namiesto záveru
Emulácia natívneho kódu je veľmi zaujímavá vec. Vývojár zariadenia môže ladiť firmvér bez skutočného zariadenia. Pre výskumníka je to príležitosť vykonávať dynamickú analýzu kódu, čo nie je vždy možné ani so zariadením.
Chceme poskytnúť špecialistom nástroj, ktorý je pohodlný, stredne jednoduchý a jeho nastavenie a spustenie nevyžaduje veľa úsilia a času.
Napíšte do komentárov svoje skúsenosti s používaním hardvérových emulátorov. Pozývame vás na diskusiu a radi odpovieme na otázky.
Do prieskumu sa môžu zapojiť iba registrovaní užívatelia.
Na čo používaš emulátor?
-
Vyvíjam (ladím) firmvér
-
Skúmam firmvér
-
Spúšťam hry (Dendi, Sega, PSP)
-
niečo iné (napíšte do komentárov)
Hlasovalo 7 užívateľov. 2 používatelia sa zdržali hlasovania.
Aký softvér používate na emuláciu natívneho kódu?
-
QEMU
-
Jednorožcový motor
-
Proteus
-
niečo iné (napíšte do komentárov)
Hlasovalo 6 užívateľov. 2 používatelia sa zdržali hlasovania.
Čo by ste chceli zlepšiť v emulátore, ktorý používate?
-
Chcem rýchlosť
-
Chcem jednoduché nastavenie/spustenie
-
Chcem viac možností pre interakciu s emulátorom (API, háčiky)
-
Som spokojný so všetkým
-
niečo iné (napíšte do komentárov)
Hlasovalo 8 užívateľov. 1 používateľ sa zdržal hlasovania.
Zdroj: hab.com