Under the hood of Android Emulator (appcert)

Aus /bin - basisgruppe informatik - wiki
Wechseln zu: Navigation, Suche

Learn about the Android Emulator

Important files

  • Disk Images (userdata.img, sdcard.img,
    • From emulator -help-disk-images augmented with information from [another source]:
The minimal required image files are the following:

    kernel-qemu      the emulator-specific ARM-based Linux kernel image for the Goldfish-platform 
    ramdisk.img      the ramdisk image used to boot the system (including Android-specific /init and config files)
    system.img       the *initial* system image (a YAFFS2 image mounted as '/')
    userdata.img     the *initial* data partition image (a YAFFS2 image mounted as '/data')

  It will also use the following writable image files:

    userdata-qemu.img  the persistent data partition image
    system-qemu.img    an *optional* persistent system image
    cache.img          an *optional* cache partition image
    sdcard.img         an *optional* SD Card partition image
    snapshots.img      an *optional* state snapshots image

Tools, Commands and parameters

  • $ANDROID_TARGET=$ANDROID_SDK/platforms/android-7

Create an 1GB SD-Card-Image (FAT32)

mksdcard -l mySDcard 1G sdcard.img

Run the emulator from console without using the -avd layer which hides the internal assembly of system-files and parameters

emulator -sysdir $ANDROID_TARGET/images 
-kernel $ANDROID_TARGET/images/kernel-qemu 
-datadir $ANDROID_AVD 
-ramdisk $ANDROID_TARGET/images/ramdisk.img 
-system $ANDROID_TARGET/images/system.img 
-data $ANDROID_AVD/userdata-qemu.img 
-cache $ANDROID_AVD/cache.img 
-sdcard $ANDROID_AVD/sdcard.img 
-snapstorage $ANDROID_AVD/snapshots.img 
-qemu -monitor stdio

Run the emulator with a preconfigured AVD-device and QEMU-monitor enabled:

emulator -avd Droid2.1 -qemu -monitor stdio

Get more informations about parameters by typing emulator -help.

Save and load States of The Android emulator

Thanks to QEMU, it is possible to directly run the emulator from a saved state.

  • First, search for the file snapshots.img. It should be in $ANDROID_SDK/tools/lib/emulator
  • Copy the file to the directory, where you created your AVD (Androiv Virtual Device).
  • Run the emulator from hand, as before.
  • Do something on the device. When you reached a state where you want to save the VM, switch to the QEMU-console (it's in the terminal, where you run the emulator-command) and type:
savevm 1
savevm pocketcampus
  • You can close the VM and restart exactly from this state with the following changes in qemu-parameters:
emulator -verbose 
-qemu -loadvm pocketcampus -monitor stdio

You can also do it in Eclipse with the AVD Manager, which is described here.

The Android root shell

Run the emulator with a Android root shell enabled:

emulator -avd Droid2.1 -shell

Run Applications from command line:

am start -a android.intent.action.MAIN -n <package>/<component>
am start -a android.intent.action.MAIN -n org.pocketcampus/org.pocketcampus.gui.mainscreen.HomeScreenSwitcher

The Android emulator starting process

How to get the source code

  • I use the sources of GIT revision: 7d3a6b4096f0295c213e1f0662107ebfd2a38a06 (23.03.2011 - 12:40)
  • Make a working directory ~/work. Inside the folder, perform the following: (Details: here)
  • repo init -u git://android.git.kernel.org/platform/manifest.git
  • repo sync <project name>
    • For the emulator, <project name> is: platform/external/qemu
    • You can view all available project names in ~/work/.repo/manifest.xml
    • Browse all Android source files here
  • read README and INSTALL to get instructions how to compile the emulator without having the whole Android build machinery configured. Basically, it's android-configure --sdl-config<...> but "Android's patched libSDL" needs to be built before.

The Emulator Front-end

Program Flow Diagram of android-vl.c/main()

The main purpose for the front-end is parsing and verifying the input parameters and pass a clean list of parameters to QEMU to start emulation.

There is alot of checking and sanitizing before it comes to emulation because different environments and changes in the emulator itself which need to be somehow backward-compatible. When porting it to normal QEMU one can drop alot of dependency checking to AVD and to the Android SDK. Anyhow, some aspects of it are explained now:

First, android/main.c/main(): execution starts. Next important thing which happens is the call of android/cmdline-option.c/android_parse_options(...): The passed arguments are parsed in an AndroidOptions structure opts, which for example contains a pointer to the ParamList structure (a linked list of parameters, where each param is a pointer to a character array).

  • The AndroidOptions structure contains alot of makros which simplify the access to params. The makros are defined in android/cmdline-options.h.
  • The parsing happens in a while loop, the current parameter is accessible via char * arg.
  • android/utils/misc.c/buffer_translate_char(arg2_tab, sizeof(arg2_tab), arg, '-', '_'): All '-' inside parameters are replaced by '_' and stored in arg2_tab.
  • Param is verified (either it is a Flag like '-no-snapshot' or a Param like '-sysdir', in the last case an argument must be given - like a name or a path)

Then: android/main-common.c/sanitizeOptions(opts) handles combinations of parameters which mix up old version parameters (like -image) with new version parameters (like -system).

Now, android/main-common.c/createAVD is called and returns a pointer to an AvdInfo structure. The AvdInfo structure makes a difference between two cases:

  1. The emulator is started in the context of the Android build system, then some standard paths which contain standard configurations are important (target architecture, output directory, build root,...). We are not interested in this case.
  2. The emulator runs stand-alone, then still deviceName, sdkRootPath, and some ini-files are required.

XXX: Is the Android SDK really needed to use the emulator? I don't think so. IMHO, you just need all image and config files. This StackOverflow-Page confirmed my hypothesis.

For both cases, additional informations are stored (apiLevel, skinName, the path to the important hardware.ini). The whole struct is defined in android/avd/info.c.

If no -sysdir is specified, it tries to find a system.img with the help of the SDK-dir (_getSdkImagePath). Similar for -system -kernel -ramdisk, -datadir, -data and -snapstorage

Then, android/avd/info.c/avdInfo_newForAndroidBuild( androidBildRoot, androidOut, params)is called. It set ups the AvdInfo* with all important stuff, especially the path where the auto-generated CoreHwIni is stored. Basically the path depends on android_build_out. In the standalone-case it is the path specified in -sysdir.

Then comes skin-Handling in android/avd/info.c/avdInfo_getSkinInfo. Since this is not very important for us, I skip it.

Now we come to the important part: HardwareConfiguration. There is a global variable called android_hw which is used as hw. The type is AndroidHwConfig, a structure which is defined in: android/avd/hw-config.h and hw-config-defs.h. The last file is autogenerated with android/tools/gen-hw-config.py. But this script does nothing else than parsing android/avd/hardware-properties.ini. IMHO, a bit indirect :). A short summary of all hardware properties is attached in the appendix.

avdInfo_initHwConfig(avd,hw) is called and should return an integer >=0. What happens inside?

  • android/avd/hw-config.c/androidHwConfig_init(hw, i->apiLevel) defines Macros for HWCFG_(BOOL|INT|STRING|DOUBLE|DISKSIZE) and adjusts a special case for apiLevel 12.
  • androidHwConfig_read prepares Makros for reading the hardware parameters from the ini-file into the hw.
  • Now follows the include statement of hw-config-defs.h, the file which contains all the hardware properties for the device in the form of HWCFG_*-Makrocalls. So, automagically the makros are expanded and do the hardware configuration inside hw. Clever.

What is missing is the generation of arguments for QEMU's main method qemu_main().

  • Since the hardware configuration also contained default values for kernel path, ramdisk, system partition, etc. they need to be overwritten with what the user entered so far. This is done now. Proper arguments are stored in a character array args (with a maximum of 127 arguments). (There is special heuristics for ramsize and heapspace which depend on the number of pixels).
  • But not all is simply passed as argument. In fact, more android-specifiy arguments are summarized written in an ini-file with androidHwConfig_write(hw,hwIni) and passed to QEMU with -android-hw. This has been used more excessively from the Android SDK team, so the command line looks very clean now:
./emulator -savevm-on-exit default-boot -show-kernel -monitor stdio -android-hw /home/aka/Android/android-sdk-linux_x86/platforms/android-7/hardware-qemu.ini

instead of something like this, which is generated from the emulator version which comes with the current Android SDK (r10):

emulator -kernel /home/aka/Android/android-sdk-linux_x86/platforms/android-7/images/kernel-qemu -initrd /home/aka/Android/android-sdk-linux_x86/platforms/android-7/images/ramdisk.img -nand system,size=0x4a00000,initfile=/home/aka/Android/android-sdk-linux_x86/platforms/android-7/images/system.img -nand userdata,size=0x4200000,file=/home/aka/.android/avd/Droid2.1.avd/userdata-qemu.img -nand cache,size=0x4200000,file=/home/aka/.android/avd/Droid2.1.avd/cache.img -hda /home/aka/.android/avd/Droid2.1.avd/sdcard.img -hdb /home/aka/.android/avd/Droid2.1.avd/snapshots.img -savevm-on-exit default-boot -show-kernel -serial android-kmsg -serial stdio -serial android-qemud -lcd-density 160 -append qemu=1 console=ttyS0 androidboot.console=ttyS1 android.checkjni=1 android.qemud=ttyS2 -m 96 -clock unix -android-avdname <build> -android-hw /tmp/android/emulator-N2nuyx

Before qemu_main() is called, SDL is initialized in android/main-common.c/ init_sdl_ui(...). SDL_Init is called. Then qemulator_init initializes the QEmulator structure (defined in android/qemulator.h). It's mainly about window sizes, skins, trackball, lcd brightnessm "onion overlays" etc. That's too high-level for us at this stage.

The modified QEMU

From the front-end-code inspection we know already that there is a non-default argument -android-hw. In this step are are analysing what is modified in order to process this argument and to emulate Goldfish hardware and boot Android.


  • First, a GNU library call to clock_gettime(CLOCK_MONOTONIC, &ts) to find out if rt_clock is used or not. ts is a timespec struct which provides seconds and nanoseconds. This is important to later retrieve the time in qemu-timer.c/get_clock() with a GNU library call, or (as so in Windows environments) use gettimeofday.
  • Then three clocks are initialized: a realtime clock, a virtual clock and a host clock. All three are used many times later on in the code.

Then, in non Windows environments, the Signal SIGPIPE is ignored.

QEMU List: vm_change_state_entry

The head of a list (or queue) is initialized with QLIST_INIT: vm_change_state_head. The type of the elements in the list is vm_change_state_entry, which include a VMChangeStateHandler, a void* opaque and due to the Makro QLIST_ENTRY: pointers to other elements (next and previous elements).

Initialize Machine: module_call_init(MODULE_INIT_MACHINE)

First: Retrieve a ModuleTypeList which is a List of ModuleEntry-Elements. A ModuleEntry can have one of the following module_init_types: MODULE_INIT_BLOCK, MODULE_INIT_DEVICE, MODULE_INIT_MACHINE, MODULE_INIT_MAX. Here it is MODULE_INIT_MACHINE. And probably the most important thing in this ModuleEntry structure is the init function. And then, for each ModuleEntry the init function is called.

XXX: I don't see where actually it is defined that now android_arm_init() is called. Usually, a pointer to the init function is done in register_module_init(<pointer to init>, module_init_type type). OK, there is a Makro call in android_arm.c: machine_init(android_arm_init) which calls a function do_qemu_init_android_arm_init which calls what we want: register_module_init(android_arm_init, MODULE_INIT_MACHINE); The missing key is: Where in android_vl.c the android_arm.c is included such that machine_init(android_arm_init) is called?

Anyhow, what happens in android_arm_init()? qemu_restister_machine(&android_arm_machine) is called. android_arm_machine is a QEMUMachine structure with name, description, a pointer to the next machine (which is NULL here) and probably most important: A pointer to QEMUMachineInitFunc which is android_arm_init_ here. Then find_default_machine() is called and just gives back the machine registered above (since it is defined with default=1). Then, virtual consoles are initialized. One at monitor_device, one at serial_device[0] and one at parallel_devices[0].

Initilaize QEMUD Multiplexing Daemon and register service boot-properties

boot-properties.c/boot_property_init_service() is called. The name is speaking because a service "boot-properties" is registered in the Android-specific QEMUD with qemud_service_register. The interesting arguments to register a service are callbackfunctions: one opaque function which is not used in boot-properties, one QemudServiceConnect which is called "whenever a new client tries to connect to the service", and two functions which handle saving and loading snapshots of the VM.

This is the first opportunity to get some knowledge about QEMUD. This daemon aims to mitigate communication between guest machine and emulator instead of implementing everything in the kernel. this is at least, what the Android emulator teams says in the very good documentation.

The function first initializes QEMUD with android_qemud_init, if android_qemud_cs is null. android_qemud_cs is a pointer to a CharDriverState. I think the main point here is to have a character pipe which establishes a connection between the tty of the emulated system and the Qemud multiplexer. This is done with charpipe.c/qemu_chr_open_charpipe(...) (XXX: to be analyzed) and qemud_multiplexer_init(...)

To summarize: The function boot_property_init_service() creates and returns a QemudService object. This is how the boot-properties service is initialized. According to the documentation, the purpose of this service is "to set system properties in the emulated system at boot time. It is invoked by the 'qemu-props' helper program that is invoked by /system/etc/init.goldfish.rc." I guess, most of the hardware properties which are defined in the qemu-hardware.ini can be modified.

QEMU Option Checking

What happens now is a parsing of all given options in argv. They do it with two nested for-loops. In the inner for loop the first option is compared with all allowed options (defined in qemu-options.h) until there is a match. Then it breaks out of the inner loop and continues in the outer loop.

In the outer loop, it first checks if required arguments are given. Arguments are stored in optarg. A big switch-statements handles the particular options to bring the emulator in the state which reflects the given option. A few examples:

  • the option "-savevm-on-exit" with the argument "default-boot" leads a global variable to be set to "default-boot".
  • "-show-kernel" leads to call android_kmsg_init(ANDROID_KMSG_PRINT_MESSAGES).
  • -monitor: monitor_device = optarg; (= stdio)
  • -android-hw: android_op_hwini = optarg; (= "...images/hardware-qemu.ini". Note that this is actually an important android-specific option.

Handling the IniFile for Android hardware configuration

Parsing: After the long switch-statement, the given ini-file is read and parsed into a IniFile structure. This occurs in android/utils/ini.c/iniFile_newFromFile(<inipath>). Parsing and population of the data structure occurs in iniFile_newFromMemory(...).

Apply to AndroidHwConfig: The IniFile is only used temporary; the main goal is populating AndroidHWConfig: android_hw. First all available options of the given device are initialized with the default values. This happens with androidHwConfig_init(...) and writes into android_hw. Then, default values are replaced by values of the ini-file androidHwConfig_read(...).

Initialize system.img: Size and location of system.img is stored in sysImage, initImage and sysBytes. This information is used to call hw/goldfish_nand.c/nand_add_dev("system,size=0x4a00000,initfile=.../system.img"). Again, arguments are parsed. A tempfile "/tmp/.android-emulator-XXXXXX" is created. Reading and writing is tested for system.img. Essentially, this is done in the following steps:

  • First of all, there is a nand_dev structure with devname, a data-buffer for read/write actions to the underlying image, a file dscripor, flags, page_size,erase_size (the size of the data buffer), ... and maximum capacity for the image.
  • Open read-only initial system.img and get a file descriptor (initfd) which is an integer. -1 represents an error.
  • Open working system partition - which is the temp dir! - and get a file descriptor (rwfd).
  • Read from initial system.img into dev->data with goldfish_nand.c/do_read(initfd, dev-data, size) which essentially is a unistd.h/read(fd,buf,size).
  • Write the written data in the working system partition with do_write(rwfd, dev->data, read_size).
  • This happens until less then the size of the buffer is successfully read, because this means we are at the end of the file.
  • close connection to initial system.img
  • store file descriptor of working system partition in dev->fd.

As you can see, the whole image is read from system.img and written in the temp file.

Initialize data.img: This is similar to the the system partition with nand_add_dev("userdata,size=0x4200000,file=...userdata-qemu.img") except: the working data partition is not a tempfile but a specified userdata.img which is not lost when the emulator is closed. This way, installed apps can be used later and settings are not lost.

Setup sdcard.img as drive: A filelock is created to avoid that multiple emulator instances corrupt data image. This happens with filelock_create(".../sd.img"). Unlocking can be done with filelock_release(".../sd.img"). If locking is successful, blockdev.c/drive_add is called. (I have not found out what it does exactly, but it modifies options and if we have specified other "drive"-options in the arguments, these would be added to the optionsstring.)

Setup snapshot.img as drive: Similar to sdcard.img.

Set heapspace of dalvikVm: Based on the configuration, "dalvik.vm.heapsize" is passed as a new boot property with boot_property_add(...). Default is 16MB.

Other options: NetworkSpeed, Network Latency, LCD Density (with a boot property qemu.sf.lcd_density), TCP dump (for capturing network traffic to a file). I skip this for now.

Initialize GSM Modem and GPS If enabled in the hw config, the gsm modem is initialized with android_qemud_get_channel( ANDROID_QEMUD_GSM, &android_modem_cs ). Similar as with the boot-properties-service, we initilize a CharDriverState android_modem_cs and register a QEMUD service. We do the same for GPS.

Retrieve DNS Server for SLIRP The emulated system needs to resolve names to ip adresses and needs a nameserver for this. For Unix environments it looks in /etc/resolv.conf of the host and look for a nameserver with slirp-android / slirp.c / slirp_get_system_dns_servers().

Initialize cache.img: Similar as system.img and data.img, the cache partition is configurated with nand_add_dev("cache,size=0x4200000,file=/.../cache.img").

Hardware and Kernel Initialization

ttyS0: The first serial port is used to transmit kernel messages from the emulated system to the emulator. What happens technically is that a CharDriverState with the label "android-kmsg" is initialized for this port. A CharDriverState is an object which models a character stream. Here it is attached to another CharDriverState, the KernelLog CharDriverState. This is a specific Android emulator concept which is called Charpipe, where you can write as much data as you want with qemu_chr_write() and don't have to care about buffering issues.

ttyS1: The second serial port is used as a channel for the qemud multiplexing daemon. Various Clients in the emulated system can access QEMUD services in the emulator. The CharDriverState is therefore called "android-qemud".

main loop initialization: Both serial ports are flagged with 0_NONBLOCK with fcntl_setfl(...). A blocking read or write-operation means that when you call a read operation and there is no data yet, the process will sleep. In the QEMU main loop there must not be a blocking operation. Non-blocking read and write operations return a -EAGAIN if there is no data (read) or if the write buffer is full (write). Responsible functions: qemu_init_main_loop() and qemu_event_init().

timer alerm initialization: Here you have a qemu_alarm_timer struct with functions start(timer), stop(timer), rearm(timer), a flag which indicates if the timer is expired and if it is pending. In a linux environment, a hpet timer is preferred which populates the structure with a hpet_start_timer-function and a hpet_stop_timer function. In my environment, it was the unix timer which worked. (for more informations: qemu-timer.c). The timer is stored in a global pointer alarm_timer (if qemu-timer.c is present). XXX: What's this? qemu_add_vm_change_state_handler(alarm_timer_on_change_state_rearm, t);

Initialize the Dynamic Translator cpu_exec_init_all() calls cpu_gen_init() calls tcg_context_init(tcg_ctx) calls tcg_target_init(tcg_ctx). Global Prologue and Epilogue is initialized. this depends on the TCG target (i.e., on the host machine). Interesting, the translation block size is 0*1024*1024=0 when i run it.

Block Driver Initialization Block Driver Initialization is triggered with module_call_init(MODUL_INIT_BLOCK). The central structure is BlockDriver (block.c). Different init-methods are called:

  • Block Driver for files (raw-posix.c)
  • BlockDriver for host_devices (raw-posix.c)
  • BlockDriver for host_floppy
  • BlockDriver for cdrom
  • Block Driver for all supported image formats (raw, vvat, dmg, cloop, qcow, qcow2, ...)

Device Initialization is triggered with module_call_init(MODULE_INIT_DEVICE) with initializing the following devices:

  • armv7m_nvic (nested vectored interrupt controller)
  • smc91c111 (network chip)

Each device is registered over qdev_register(DeviceInfo* info). QEMU does not require devices to know in advance the board for which it is emulated. They only must know the Bus Type, here it is a System Bus (others are Pci-Bus, SCSI-Bus, I2C-Bus, SSI-Bus). If we take the example armv7m_nvic: The device has to register itself to the sysbus and give an initialization function. Moreover it has to register to the qemu platform with qdev_register. But here, the sysbus_device_init function is used. (Related source files: sysbus.c, qdev.c, smc91c111.c, armv7m_nvic.c).

Android ARM Init The Android emulator uses a "arm926" cpu-model, which is initialized in android_arm_init(). The important structure is CPUState alias CPUARMState. The state of an ARM CPU models 16 general purpose registers, banked registers, direct access to the program status register CPSR and a cache for CPSR flags for faster access. This corresponds more or less to the specification of what is an ARM CPU. Also included in the CPUARMState structure is the Coprocessor CP15. Moreover, a few common variables are added through CPU_COMMON: a pointer to the currently executing translation block(TB), the TB cache, a pointer to another CPU state with the same TB cache, etc. (See cpu-defs.h).

The ARM CPU has also the VFP feature which means it has an Coprecessor for vector floating points.

Related source files: hw/android_arm.c, helper.c, cpu.h, cpu-defs.h

Initialize Goldfish hardware

Android emulator overview.png
  • RAM is allocated and associated with the CPU. * In arm_pic_init_cpu() two top-level IRQs are allocated which are handled by arm_pic_cpu_handler. This is for the Programmable Interrupt Controller. ARM has two IRQ modes: IRQ and FIQ (fast interrupt). An FIQ has high priority and can even make an running IRQ interrupt. IR

Goldfish PIC Initialization goldfish_interrupt_init() is called with a base adress: 0xff000000, the parent IRQ and the parent FIQ States which were created before. This is used to populate the Structure goldfish_int_state which mainly contains a goldfish_device structure which will also be important for all other Goldfish devices. because each is registered with goldfish_device_add(goldfish_device, readfn, writefn). A few explainations:

  • readfn and writefn point to an array of three functions (first function for access a byte, second function to access a word and third function to access dword) of the type CPUReadMemoryFunc and CPUWriteMemoryFunc. According to the interface of these functions in cpu-common.h, they are used to read and write the memory. For the Goldfish PIC, each array contains three times the same function: goldfish_int_read and goldfish_int_write. They enable to read the number of pending interrupts or the interrupt number.
  • goldfish_add_device calls goldfish_add_device_no_io and then cpu_register_io_memory_fixed with io_index 0 which means that a new io-zone in the memory is created. To summarize: MMIO (memory-mapped IO) is alloced and the functions are assigned to the IO-zone (take a look at the global 2D-array pointers io_mem_write and io_mem_read in exec.c).

Releated source files: arm-pic.c, android-arm.c, irq.c, goldfish_interrupt.c, goldfish_device.h, cpu-common.h, exec.c, cpu-all.h

Goldfish Device Init Global variables are initialized for the PIC (goldfish_pic), the free base (i guess for allocating new interrupts: goldfish_free_base = 0xFF010000) and the number of free irqs (goldfish_free_irq = 10).

Goldfish Bus Init The Goldfish bus is initialized with the base Adress 0xFF001000 and 1 irq, which is reflected in the global structure bus_state which only stores the goldfish_device object of the bus. Again, we have a goldfish_device_add call with read and write function arrays: The functions are more sophisticated than the pic functions with 6 different reading modes communicated over 'offset'-parameter. The main structure for reading and writing is bus_state.

Goldfish timer and RTC Init The base adress is: 0xFF003000 with 3 IRQs. A new timer is created and the global struct timer_state is populated with a reference to this timer. Again, the two devices (goldfish_timer and goldfish_rtc) are added with goldfish_device_add and io regions are allocated and mapped. The read and write functions are again comprehensive. One can read the low bits or the high bits of current time. First you read the low bit and you get the corresponding high bit exactly for the time when you have read the low bit (to get a consistent overall timer). The write functions can set an alarm, clear an alarm and clear an interrupt.

Goldfish Serial Port Init (TTY) This happens for each formerly created serial port: Similar as above, a tty_state structure with a contained goldfish_device structure is populated. It's a bit different, because there is also the CharDriverState (cs) which is populated. Moreover, handlers are added to cs which are called when there is something to receive: tty_receive therefore copies a given buffer to the charDriver state when called. It also seems that an interrupt is set if there is data available and the CharDriverState is ready. This happens with goldfish_device_set_irq(device, irq=0, level=1). Such a call is translated into qemu_set_irq(<base irq number of goldfish_pic>+irq,level) which triggers a call to the corresponding qemu_irq_handler which is assigned to each IRQState, in this case it would probably be the arm_pic_cpu_handler. This looks if it is an irq (0) or an fiq(1). In this case it is an irq. Since level=1: cpu_interrupt would be called (if it was 0, cpu_reset_interrupt would be called). The mask of this call would represent a hardware interrupt (because we emulate a Programmable Interrupt Controller here). For other masks (like FIQ-Interrupt or virtual interrupt) see: cpu-all.h. And now, supposed everything happens like that, the CPUState of the machine itself is now modified in cpu_interrupt, to be exact: the interrupt_request field is modified according to the mask. Ok, this brought some light into interrupt handling for goldfish.

Ethernet smc91c111 init The ethernet card is initialized without i/o-region, so there is only a call to goldfish_add_device_no_io with a normal goldfish_device structure. Then smc91c111_init is called where there is a new course of function calls which seem to be a usual QEMU device initialization. qdev_create creates a DeviceState which creates a number of other structs. One important is DeviceType which has a structure DeviceInfo. DeviceInfo stores an init function, in our case: sysbus_device_init which calls in turn smc91c111_init1 but with a SysBusDevice as parameter. I/O memory is allocated and registered. Handlers smc91c111_readfn and smc91c111_writefn are attached. But the card itself has its state structure: smc91c111_state which is populated.

Goldfish Framebuffer init The name of the new device is "goldfish_fb" and the structure is goldfish_fb_state wich contain beneath the usual goldfish_device structure also a DisplayState structure. The graphic console is initialized and then like other goldfish devices, the goldfish_device_add is called.

Goldfish MMC init The name of the device is "goldfish_mmc_init". The structure is goldfish_mmc_state which also includes a BlockDriverState. goldfish_device_add as usual.

Goldfish Battery init The goldfish_battery_state structure is initalized with 50% capacity. Procedure is similar to other goldfish devices.

Goldfish Events Init The goldfish_events device is initialized with goldfish_add_device_no_io and events_dev_init. The latter function uses the android_hw config to set key press events according to available hardware. Let's take the home-key as example. Each virtual android device should emulate such a button with a keypress. To achieve this, events_set_bit(s, EV_KEY, KEY_HOME) is called.

Goldfish NAND Init goldfish_nand is first initialized as a no-io-device but as a second step in nand_dev_init the i/o memory zone is assigned.

Goldfish Switch Init goldfish_switch is initialized as a goldfish device. The name of the switch is "TEST". Also a second switch "TEST2" is initialized with switch_test_write as a write function. Not sure what's the purpose of such switches.

Related source files: android-arm.c, goldfish_device.c, goldfish_timer.c, goldfish_tty.c, smc91c111.c, qdev.c, net.h, goldfish_fb.c, console.h, goldfish_mmc.c, goldfish_battery.c, goldfish_events_device.c, linux_keycodes.h, goldfish_nand.c, goldfish_switch.c.

Android Kernel preparation

arm_load_kernel is the first function which triggers the kernel load. With my kernel image (which is the default kernel image ) load_image_targphys succeeds to load the kernel image into memory beginning with address 0x10000h. Then, Initrd image is loaded into memory beginning with address 0x800000h.

The Bootloader is prepared. The machine code of the bootloader is the following:

/* The worlds second smallest bootloader.  Set r0-r2, then jump to kernel.  */
static uint32_t bootloader[] = {
  0xe3a00000, /* mov     r0, #0 */
  0xe3a01000, /* mov     r1, #0x?? */
  0xe3811c00, /* orr     r1, r1, #0x??00 */
  0xe59f2000, /* ldr     r2, [pc, #0] */
  0xe59ff000, /* ldr     pc, [pc, #0] */
  0, /* Address of kernel args.  Set by integratorcp_init.  */
  0  /* Kernel entry point.  Set by integratorcp_init.  */
  • mov r0, #0 - The first line only loads 0 into r0 because this is required by the kernel.
  • MOV r1 1441 - In the second line, one has to load the board id (machine type) into the register r1. The Goldfish platform is not a real board, but has its board id: 1441. A list of all ARM machines can be found here
  • ORR r1, r1, 1441 - Third line: continues to load the board id into r1.
    • to be more precise: 1441 in HEX is 0x5A1 (0b010110100001). First, the last 8 bits (green part) are loaded into r1 with a MOV operation. Then, the second command computes a bitwise OR between 0x0a1 and 0x500 to get the value we want: 0x5A1 (1441), which is the goldfish board id.
  • 0x100 - The sixth line contains the starting address which contains the kernel args
  • 0x10000 - The last line contains the starting address for loading the kernel

Good articles about the ARM Linux Bootloader can be found here and here and here.

After the boot loader is prepared, it is written into memory starting from adress 0.

After that, the Kernel parameters are written to RAM starting from address 0x100h (as specified in the boot loader). In, set_kernel_args(...), the Android emulator writes ATAGS in the following order:

  • ATAG_MEM (with the current RAMsize)
  • ATAG_INITRD2 (with the address of the initial ramdisk)
  • ATAG_CMDLINE (stores the arguments, in our case: qemu=1 console=ttyS0 android.qemud=ttyS1 android.checkjni=1 ndns=1)
  • ATAG_NONE (to indicate the End of the Kernel Arguments)

A Shutdown/Reset handler is initialized with qemu_register_reset. The handler is: main_cpu_reset. In the case of a reset, the emulated system is rebooted and the kernel loads again.

Related source files: arm-misc.h, arm-boot.c, elf.h

This was now the last call of android_arm_init_() and we are back at qemu_main().

QEMU Monitor Initialization

The QEMU Monitor consists of two main structures:

  • A ReadLineState
  • A CharDriverState with name "monitor"

A virtual timer (= a timer which only runs when the emulator runs) is initialized. This timer frequently triggers the function release_keys which processes pending key-presses.

The ReadLineState is initialized with a ReadLineCompletionFunction monitor_find_completion and commanded to print out the prompt-line "(qemu)" with monitor_read_command. This state seems to be responsible for everything related to user inputs (from the monitor device).

The CharDriverState (cs) was allocated earlier based on the argument of the user: "monitor stdio". stdio is the monitor device, where informations are written to and read from. This happened with qemu_chr_open("monitor", monitor_device, NULL);. Handlers are added to the cs for handling reading and handling events: monitor_can_read, monitor_read, monitor_event.

Initialize Android-specific items (ADB)

The function android_emulation_setup() initializes the Android Debug Bridge. ADB consists of three components:

  • A deamon process (adbd) in the guest system which is listening on port 5555.
  • A server process on the host system which manages the communication between client and daemon. It listens on port 5037 for client commands.
  • A client process on the host system (started with adb) which allows the user to execute various commands (like: install app.apk), accessing the Data base of the guest system, etc.

Moreover, modem and hardware sensors are initialized ( android_hw_sensors_init ).

VM is started

The function vm_start() initializes the cpu cycles. With vm_state_notify(1,0): the list of existing vm_change_state_handlers is notified. Up to this point, only the alarm_timer_on_change_state_rearm is registered for notification. alarm_timer_on_change_state_rearm rearms the timer.

Main Loop

Loop 1

the main_loop() function skips tcg_cpu_exec() the first time, because a timer alarm was pending. It exits and calculates the timeout for the main_loop_wait() function. The timeout mainly depends on the CPU state:

  • Timeout = 0 if tcg_has_work() returns 1.
  • tcg_has_work returns 1 if cpu_has_work = 1.
  • cpu_has_work = 1 if the cpu is stopped, or halted or there is an interrupt request going on (env->stop, env->halted, env->interrupt_request & interrupt_fiq, interrupt_hard, interrupt_exittb).

Then, All IO-Handlers are polled. This happens with the help of select(), a standard Unix way to handle multiple file descriptors. First, all file fescriptors are inserted in one of the three arrays with FD_SET(fd, array) which contain (1) read-, (2)write- and (3)execute file descriptors. Then, a timeout is defined and select() is called with the three arrays as parameter + a timeout (for the time to wait for an event). In the first cycle, the timeout is 0 which forces select() to return immediately. Otherwise, if select() has a chance to find a state change of one or more file descriptors, it returns an integer > 0 and the main_loop_wait() calls fd_read() or fd_write(). Find more informations here.

Then, all charpipes are polled. Charpipes contain two CharDriverState-structs. (For example: tty <-> qemud, kernel_log <-> tty). In the first loop, there are no CharBuffers to poll.

Then, all timers are set to run. Since the vm_clock is not active, it is skipped. But the rt_clock is active and the timer is not outdated. It is removed from the list of active timers and then the callback (a QEMUTimerCB structure) is called. The callback is gui_update. It refreshes the display state with android_display_refresh(). The Display State contains a Framebuffer which is polled in qemulator_fb_poll(). Polling means, qemulator_refresh() is called. It is checked if the content of the VGA framebuffer changed with the following function cascade: qframebuffer_check_updates(), vga_hw_update(), goldfish_fb_update_display().

Then, all bottom-halfes are polled of the current AsyncContext are called. I am not sure exactly why there are different AsyncContexts needed, but that's not in my business for now. Three times the BottomHalf qemu_chr_generic_open_bh is called, with three different CharDriverStates (CS). For each CS, the CHR_EVENT_OPENED-Event is called and it's EventHandler is called. One of the tree CharDriverStates is associated with the QEMU Monitor. Thus, monitor_event is called and prints out the introducation lines and the Monitor prompt.

Loop 3

In Loop 2, nothing interesting happens. Loop 3 goes deeper in the Dynamic Binary Translation machinery. tcg_cpu_exec(env) -> qemu_cpu_exec(env) -> cpu_arm_exec(). From the perspective of the emulator, there are two main points Generate Translation Block and Exception Handling:

Find/Generate Translation Block
tb = tb_find_fast();

This line finds the next Translation Block (TB). To find the next TB there are the following alternatives:

  • The TB is already in the cache table: env->tb_jmp_cache[tb_jmp_cache_hash_func(pc)].
  • The TB can be found with "physical mappings" (?). This happens in tb_find_slow(pc, cs_base, flags).
  • The TB is not found and has to be generated with tb_gen_code(). The TB is generated in two steps: Generate the intermediate code from the guest code (gen_intermediate_code(env, tb)) and generate the host code from the intermediate code (tcg_gen_code(s, tb->tc_ptr;)). For the first part, the target-specific file target-<ISA-guest>/translate.c is needed. For the second part, the tcg-common-file (tcg/tcg.c) and the host-specific file (officially, a tcg-target: tcg/<ISA-host>/tcg-target.c) is needed. In our case, <ISA-guest> is ARM and <ISA-host> is i386 or x86_64.

Then, it is tried to link TB's together with the next TB to gain performance improvements: tb_add_jump(...). The TB is executed: env->current_tb = tb; next_tb = tcg_qemu_tb_exec(tc_ptr);.

More details on the inner working of DBT:

Exception Handling

The main point in this loop are the following three lines:

                    env->exit_request = 0;
                    env->exception_index = EXCP_INTERRUPT;

It seems that an Interrupt Exception is set and cpu_loop_exit() is called which jumps to the exception handling code (which was initialized before with a setjmp()-call). There, the exception_index is stored, execution breaks out of exception handling loop and lands back at tcg_cpu_exec(), but here the return value is not used. Again, execution enters main_loop_wait() with a timeout of 0. Nothing useful is done there.

Translated Guest Code

With the qemu-parameter "-d" it is possible to see which guest code is translated into which micro operations and into which host instructions. I use the following to find out more about the first translated instructions:

-d in_asm,out_asm,op,op_opt,int,cpu,exec

Possible log items (for QEMU 0.10.5, newer versions have added 2 more items):

out_asm    show generated host assembly code for each compiled TB
in_asm     show target assembly code for each compiled TB
op         show micro ops for each compiled TB
op_opt     show micro ops before eflags optimization and after liveness analysis
int        show interrupts/exceptions in short format
exec       show trace before each executed TB (lots of logs)
cpu        show CPU state before block translation

The instructions are then saved in /tmp/qemu.log. When you are debugging the main routine, you can examine the translation process step by step with the help of tail -f /tmp/qemu.log:

0x00000000:  mov	r0, #0	; 0x0
0x00000004:  mov	r1, #161	; 0xa1
0x00000008:  orr	r1, r1, #1280	; 0x500
0x0000000c:  ldr	r2, [pc, #0]	; 0x14
0x00000010:  ldr	pc, [pc, #0]	; 0x18

With the generated output of main loop cycle #3, you see that the emulated ARM CPU starts at 0x00000000 and it executes the bootloader we have covered in an earlier section. 0x00000000 is the exception vector for the reset exception. That basically means that if there is an reset exception, the normal execution is stopped, the programme state is stored in backup registers and execution continues at the exception vector. Since reset exception is always handled in the supervisor mode, I also assume that currently the emulated ARM CPU behaves like it is running in supervisor mode. Since we also log cpu state we get the current state of the cpsr register: PSR=400001d3. Decoded in binary values we have: 0b01000000000000000000000111010011. The last 5 digits represent the M flag which indicate the processor mode (cf., ARMv5TE-specification,A2-11 or take a look at my cpu model summary of the ARM-target).

This code is translated into the following micro operations:

 movi_i32 tmp8,$0x0
 st_i32 tmp8,env,$0x0
 movi_i32 tmp8,$0xa1
 st_i32 tmp8,env,$0x4
 movi_i32 tmp8,$0x500
 ld_i32 tmp9,env,$0x4
 or_i32 tmp9,tmp9,tmp8
 st_i32 tmp9,env,$0x4
 movi_i32 tmp8,$0x14
 qemu_ld32u tmp9,tmp8,$0x0
 st_i32 tmp9,env,$0x8
 movi_i32 tmp9,$0x18
 qemu_ld32u tmp8,tmp9,$0x0
 movi_i32 tmp10,$0x1
 and_i32 tmp9,tmp8,tmp10
 st_i32 tmp9,env,$0xd0
 movi_i32 tmp10,$0xfffffffe
 and_i32 tmp8,tmp8,tmp10
 st_i32 tmp8,env,$0x3c
 exit_tb $0x0

And finally into this long chain of X86 instructions:

OUT: [size=154]
0xf60c5000:  xor    %eax,%eax
0xf60c5002:  mov    %eax,0x0(%ebp)
0xf60c5005:  mov    $0xa1,%eax
0xf60c500a:  mov    %eax,0x4(%ebp)
0xf60c500d:  mov    0x4(%ebp),%eax
0xf60c5010:  or     $0x500,%eax
0xf60c5016:  mov    %eax,0x4(%ebp)
0xf60c5019:  mov    $0x14,%ecx
0xf60c501e:  mov    %ecx,%edx
0xf60c5020:  mov    %ecx,%eax
0xf60c5022:  shr    $0x6,%edx
0xf60c5025:  and    $0xfffffc03,%eax
0xf60c502b:  and    $0xff0,%edx
0xf60c5031:  lea    0x530(%edx,%ebp,1),%edx
0xf60c5038:  cmp    (%edx),%eax
0xf60c503a:  mov    %ecx,%eax
0xf60c503c:  je     0xf60c5047
0xf60c503e:  xor    %edx,%edx
0xf60c5040:  call   0x813c9d0
0xf60c5045:  jmp    0xf60c504c
0xf60c5047:  add    0xc(%edx),%eax
0xf60c504a:  mov    (%eax),%eax
0xf60c504c:  mov    %eax,0x8(%ebp)
0xf60c504f:  mov    $0x18,%ecx
0xf60c5054:  mov    %ecx,%edx
0xf60c5056:  mov    %ecx,%eax
0xf60c5058:  shr    $0x6,%edx
0xf60c505b:  and    $0xfffffc03,%eax
0xf60c5061:  and    $0xff0,%edx
0xf60c5067:  lea    0x530(%edx,%ebp,1),%edx
0xf60c506e:  cmp    (%edx),%eax
0xf60c5070:  mov    %ecx,%eax
0xf60c5072:  je     0xf60c507d
0xf60c5074:  xor    %edx,%edx
0xf60c5076:  call   0x813c9d0
0xf60c507b:  jmp    0xf60c5082
0xf60c507d:  add    0xc(%edx),%eax
0xf60c5080:  mov    (%eax),%eax
0xf60c5082:  mov    %eax,%edx
0xf60c5084:  and    $0x1,%edx
0xf60c5087:  mov    %edx,0xd0(%ebp)
0xf60c508d:  and    $0xfffffffe,%eax
0xf60c5090:  mov    %eax,0x3c(%ebp)
0xf60c5093:  xor    %eax,%eax
0xf60c5095:  jmp    0x8454c28

Loop 7-10: Kernel takes over


R15 (Program Counter) becomes 0x10000. R2 contains the Starting adress of the Kernel Parameters.

The upcoming loop (#9) already executes at 0x1000:

0x00010000:  nop			(mov r0,r0)
0x00010004:  nop			(mov r0,r0)
0x00010008:  nop			(mov r0,r0)
0x0001000c:  nop			(mov r0,r0)
0x00010010:  nop			(mov r0,r0)
0x00010014:  nop			(mov r0,r0)
0x00010018:  nop			(mov r0,r0)
0x0001001c:  nop			(mov r0,r0)
0x00010020:  b	0x10030
 ld_i32 tmp8,env,$0x0
 st_i32 tmp8,env,$0x0
 ld_i32 tmp8,env,$0x0
 st_i32 tmp8,env,$0x0
 ld_i32 tmp8,env,$0x0
 st_i32 tmp8,env,$0x0
 ld_i32 tmp8,env,$0x0
 st_i32 tmp8,env,$0x0
 ld_i32 tmp8,env,$0x0
 st_i32 tmp8,env,$0x0
 ld_i32 tmp8,env,$0x0
 st_i32 tmp8,env,$0x0
 ld_i32 tmp8,env,$0x0
 st_i32 tmp8,env,$0x0
 ld_i32 tmp8,env,$0x0
 st_i32 tmp8,env,$0x0
 goto_tb $0x0
 movi_i32 tmp8,$0x10030
 st_i32 tmp8,env,$0x3c
 exit_tb $0xf4f84064
OUT: [size=71]
0xf60c50a0:  mov    0x0(%ebp),%eax
0xf60c50a3:  mov    %eax,0x0(%ebp)
0xf60c50a6:  mov    0x0(%ebp),%eax
0xf60c50a9:  mov    %eax,0x0(%ebp)
0xf60c50ac:  mov    0x0(%ebp),%eax
0xf60c50af:  mov    %eax,0x0(%ebp)
0xf60c50b2:  mov    0x0(%ebp),%eax
0xf60c50b5:  mov    %eax,0x0(%ebp)
0xf60c50b8:  mov    0x0(%ebp),%eax
0xf60c50bb:  mov    %eax,0x0(%ebp)
0xf60c50be:  mov    0x0(%ebp),%eax
0xf60c50c1:  mov    %eax,0x0(%ebp)
0xf60c50c4:  mov    0x0(%ebp),%eax
0xf60c50c7:  mov    %eax,0x0(%ebp)
0xf60c50ca:  mov    0x0(%ebp),%eax
0xf60c50cd:  mov    %eax,0x0(%ebp)
0xf60c50d0:  jmp    0xf60c50d5
0xf60c50d5:  mov    $0x10030,%eax
0xf60c50da:  mov    %eax,0x3c(%ebp)
0xf60c50dd:  mov    $0xf4f84064,%eax
0xf60c50e2:  jmp    0x8454c28

Loop 13: Are we in supervisor mode?

Find comments beneath the guest instructions

R00=00000000 R01=000005a1 R02=00000100 R03=00000000
R04=00000000 R05=00000000 R06=00000000 R07=00000000
R08=00000000 R09=00000000 R10=00000000 R11=00000000
R12=00000000 R13=00000000 R14=00000000 R15=00010030
PSR=400001d3 -Z-- A svc32

0x00010030:  mov	r7, r1   ; store board ID in r7
0x00010034:  mov	r8, r2   ; store kernel arg address in r8
0x00010038:  mrs	r2, CPSR ; get status register 
0x0001003c:  tst	r2, #3	; 0x3 (are we in one of the following modes: supervisor, abort, undefined, system)? if yes, set z to 0.
0x00010040:  bne	0x1004c ; if z is 0, jump to 0x1004c

 ld_i32 tmp8,env,$0x4
 st_i32 tmp8,env,$0x1c
 ld_i32 tmp8,env,$0x8
 st_i32 tmp8,env,$0x20
 movi_i32 tmp9,$cpsr_read
 call tmp9,$0x0,$1,tmp8
 st_i32 tmp8,env,$0x8
 movi_i32 tmp8,$0x3
 ld_i32 tmp9,env,$0x8
 and_i32 tmp9,tmp9,tmp8
 st_i32 tmp9,env,$0xc0
 st_i32 tmp9,env,$0xc4
 ld_i32 tmp8,env,$0xc4
 movi_i32 tmp10,$0x0
 brcond_i32 tmp8,tmp10,eq,$0x0
 goto_tb $0x0
 movi_i32 tmp8,$0x1004c
 st_i32 tmp8,env,$0x3c
 exit_tb $0xf4f840c0
 set_label $0x0
 goto_tb $0x1
 movi_i32 tmp8,$0x10044
 st_i32 tmp8,env,$0x3c
 exit_tb $0xf4f840c1

OUT: [size=98]
0xf60c50f0:  mov    0x4(%ebp),%eax
0xf60c50f3:  mov    %eax,0x1c(%ebp)
0xf60c50f6:  mov    0x8(%ebp),%eax
0xf60c50f9:  mov    %eax,0x20(%ebp)
0xf60c50fc:  call   0x813ad40
0xf60c5101:  mov    %eax,0x8(%ebp)
0xf60c5104:  mov    0x8(%ebp),%eax
0xf60c5107:  and    $0x3,%eax
0xf60c510a:  mov    %eax,0xc0(%ebp)
0xf60c5110:  mov    %eax,0xc4(%ebp)
0xf60c5116:  mov    0xc4(%ebp),%eax
0xf60c511c:  test   %eax,%eax
0xf60c511e:  je     0xf60c513b
0xf60c5124:  jmp    0xf60c5129
0xf60c5129:  mov    $0x1004c,%eax
0xf60c512e:  mov    %eax,0x3c(%ebp)
0xf60c5131:  mov    $0xf4f840c0,%eax
0xf60c5136:  jmp    0x8454c28
0xf60c513b:  jmp    0xf60c5140
0xf60c5140:  mov    $0x10044,%eax
0xf60c5145:  mov    %eax,0x3c(%ebp)
0xf60c5148:  mov    $0xf4f840c1,%eax
0xf60c514d:  jmp    0x8454c28

Loop #15: Yes, we are.

R00=00000000 R01=000005a1 R02=400001d3 R03=00000000
R04=00000000 R05=00000000 R06=00000000 R07=000005a1
R08=00000100 R09=00000000 R10=00000000 R11=00000000
R12=00000000 R13=00000000 R14=00000000 R15=0001004c
PSR=000001d3 ---- A svc32

Note that the Z flag is empty now. Also note that PC is set to 0x1004c. In other words: TST was successful. and the conditional Branch will be executed.

Loop #17: Disable IRQ and FIQ interrupts

0x0001004c:  mrs	r2, CPSR        ; retrieve state register
0x00010050:  orr	r2, r2, #192	; 0xc0 (0b11000000) bitwise or leads to disabling IRQ and FIQ interrupts.
0x00010054:  msr	CPSR_c, r2      ; update state register (c means, only bits of the control field will be updated.

"The control field contains two interrupt disable bits, five processor mode bits, and the Thumb bit" (ARM specification, A3-15)

Loop #19-25


Loop 27 ~ 2000

The only thing which is possible to see with the debugger is that execution writes to the Charpipe Kernel_log <-> TTY: "Uncompressing Linux .....". So in the background the kernel is already loaded.

Then a first interrupt is triggered from the emulated system and cpu_unlink_tb() is called and arm_pic_cpu_handler() comes to set the interrupt. Next loop cycle, the ARM-specific interrupt handling procedure is called and does:

                        env->exception_index = EXCP_IRQ;
                        next_tb = 0;

do_interrupt() in target-arm/helper.c does what needs to be done, depending on the type of interrupt. Moreover, it is ensured that Tranlation-Block-Chaining is disabled, because the interrupt may have changed the control flow.

TBC... further guest code will be interesting.


Concerning Native Development Kit of Android

Concerning Android emulator, Android Sources

Concerning Disassembler and DalvicVM bytecode:

Concerning Android kernel and other Android software stack components

Concerning Android boot process:

Misc Useful Information:


List of Android virtual hardware properties

Source: ~/qemus/qemu-android/android/avd/hardware-properties.ini

name        = hw.ramSize
abstract    = Device ram size

name        = hw.touchScreen
abstract    = Touch-screen support

name        = hw.trackBall
abstract    = Track-ball support

name        = hw.keyboard
abstract    = Keyboard support
name        = hw.keyboard.lid
abstract    = Keyboard lid support

name        = hw.dPad
abstract    = DPad support

name        = hw.gsmModem
abstract    = GSM modem support

name        = hw.camera
abstract    = Camera support

name        = hw.camera.maxHorizontalPixels
abstract    = Maximum horizontal camera pixels
name        = hw.camera.maxVerticalPixels
abstract    = Maximum vertical camera pixels

name        = hw.gps
abstract    = GPS support

name        = hw.battery
abstract    = Battery support

name        = hw.accelerometer
abstract    = Accelerometer

name        = hw.audioInput
abstract    = Audio recording support
name        = hw.audioOutput
abstract    = Audio playback support

name        = hw.sdCard
abstract    = SD Card support
name        = hw.sdCard.path
abstract    = SD Card image path

name        = disk.cachePartition
abstract    = Cache partition support
name        = disk.cachePartition.path
abstract    = Cache partition
name        = disk.cachePartition.size
abstract    = Cache partition size

name        = hw.lcd.width
abstract    = LCD pixel width
name        = hw.lcd.height
abstract    = LCD pixel height
name        = hw.lcd.depth
abstract    = LCD color depth
name        = hw.lcd.density
abstract    = Abstracted LCD density

name        = vm.heapSize
abstract    = Max VM application heap size

name        = hw.sensors.proximity
abstract    = Proximity support

name        = kernel.path
abstract    = Path to the kernel image
name        = kernel.parameters
abstract    = kernel boot parameters string

name        = disk.ramdisk.path
abstract    = Path to the ramdisk image

name        = disk.systemPartition.path
abstract    = Path to runtime system partition image
name        = disk.systemPartition.initPath
abstract    = Initial system partition image
name        = disk.systemPartition.size
abstract    = Ideal size of system partition

name        = disk.dataPartition.path
abstract    = Path to data partition file
name        = disk.dataPartition.initPath
abstract    = Initial data partition
name        = disk.dataPartition.size
abstract    = Ideal size of data partition

name        = disk.snapStorage.path
abstract    = Path to snapshot storage