Could a 64-bit hardware device run a 32-bit Android version? This question comes from How can I tell if my installed Android version, not CPU, is a 64 bit or 32 bit one?

On desktop computers it is possible to install a 32-bit operating system on a 64-bit hardware device i.e. you can install Ubuntu Linux 32-bit on an AMD FX(tm)-9370 Eight-Core Processor (64-bit).

Does the same case happens for Android? Is there a 32-bit and a 64-bit version for the same 64-bit device?

Some example links (like ROM downloads in 32 and 64 bits for the same Android device) would be welcome.
It's not clearly mentioned but I assume you are talking about ARM architecture. “32-bit Android OS” means a 32-bit kernel and the collection of ELF binaries and libraries which are compiled to run on 32-bit processor. This middleware stack particularly includes app_process binary which runs with name zygote and hosts the complete application framework of Android. It forks Java Virtual Machines (for every app) in which Dalvik EXecutable (.dex) code runs which is neither 32-bit nor 64-bit. But the JVMs are native processes; either 32 or 64-bit depending on the JNI libraries the app contains.

We can run 32-bit OS on 64-bit device if 64-bit:


Processor supports running 32-bit code which is usually the case because backward compatibility is desired, but not always.
Kernel supports running 32-bit code because unlike bare-metal hypervisors we - the processes on Android - don't deal directly with hardware.


PROCESSOR'S BITNESS

Every 64-bit processor's support for 32-bit execution isn't universal. For instance Samsumg Exynos 9 Series 982x SoCs contain Cortex-A55 and A75 which (are based on ARMv8.x and they) have support for aarch64 and aarch32. On the other hand Qualcomm's Centriq 2400 SoCs and Cavium's Thunder X2 SoCs also include ARMv8.x processors but without aarch32 support.

In order to find device architecture, we can read /proc/cpuinfo which exposes information from Main ID register of the processor.

~$ cat /proc/cpuinfo
...
CPU implementer : 0x41
CPU architecture: 8
...
CPU part        : 0xd03
...
Hardware        : Qualcomm Technologies, Inc MSM8953


But there in no direct information like lm flag to confirm 32/64-bit support. So we need to get technical details of hardware. As per the requirements from Google, CPU architecture: 8 indicates that it's a ARMv8 device. Also see this commit. And here is the hex to human readable decoding map which lscpu command uses.

~$ lscpu | grep -E 'Vendor |Model '
Vendor ID:           ARM
Model name:          Cortex-A53


Other possible ways to get SoC information:

~$ cat /sys/firmware/devicetree/base/model
Qualcomm Technologies, Inc. MSM8953 + PMI8950 QRD SKU3


~$ cat /sys/devices/soc0/{vendor,family,machine}
Qualcomm
Snapdragon
MSM8953


It's evident from technical specifications of Qualcomm Snapdragon 625 (MSM 8953) and Cortex-53 that it's based on ARMv8 architecture, which can process both instruction sets: aarch64 and aarch32.

KERNEL'S BITNESS

When arm64 support was added to Linux kernel, 32-bit compatibility mode was also included. If kernel is built with IKCONFIG_PROC, confirm by:

~# zcat /proc/config.gz | grep -E 'CONFIG_ARM64=|CONFIG_COMPAT=|BINFMT'
CONFIG_ARM64=y
CONFIG_COMPAT=y
CONFIG_BINFMT_ELF=y
CONFIG_COMPAT_BINFMT_ELF=y
CONFIG_BINFMT_SCRIPT=y


So the 64-bit ELF files are executed natively with binfmt_elf while 32-bit ELF files are executed with compat_binfmt_elf. See details in this answer.

Usually uname -m is used to find the architecture of device supported by kernel. It shows the persoanlity (execution domain) of the process which defaults to kernel's primary architecture (PER_LINUX ). But Linux kernel supports changing persoanlity, so this approach can be confusing:

~$ uname -m
aarch64
~$ setarch linux32 uname -m
armv8l


In latter command uname is running under 32-bit compatible personality. The same would happen if you use some app - like this - which runs with LINUX32 personality, no matter even if kernel and uname binaries are 64-bit. For details see this and this. lscpu also confirms CPU operation modes using same phenomenon.

USERCODE BITNESS

Now coming to the userspace, init is the very first process run by kernel. Lets check its bitness. If the 5th byte is 1 it's 32-bit ELF file, if it's 2 the binary is 64-bit:

~# hexdump -n5 /proc/1/exe
0000000 457f 464c 0002


Or use file command:

~# file /proc/1/exe
/proc/1/exe: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked ...


Similarly check bitness of other vital OS binaries/libraries:

~$ file /system/lib*/libc.so
/system/lib/libc.so:   ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV), dynamically linked ...
/system/lib64/libc.so: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked ...


~$ file /system/bin/linker*
/system/bin/linker:        ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV), dynamically linked ...
/system/bin/linker64:      ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked ...


OS maintains 64-bit as well as 32-bit libraries and dynamic linker because apps may contain both types of binary code (though the major part must be Java/Dalvik bytecode). For instance the dnsmasq program which serves as DHCP server on hotspot is a 64 bit binary while Google Play Services uses a 32-bit library:

~# readelf -a /system/bin/dnsmasq
  Class:                             ELF64
  Type:                              DYN (Shared object file)
  Machine:                           AArch64
      [Requesting program interpreter: /system/bin/linker64]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so]


~# readelf -a /data/data/com.google.android.gms/app_vision/ocr/libs/armeabi-v7a/libocr.so
  Class:                             ELF32
  Type:                              DYN (Shared object file)
  Machine:                           ARM
      [Requesting program interpreter: /system/bin/linker]
 0x00000001 (NEEDED)                     Shared library: [libc.so]


Similarly it's not uncommon to have 32-bit binary blobs from OEMs / SoC vendors on 64-bit ROMs. Even some basic components of AOSP aren't 64-bit supported. Take example of audioserver.
To see all processes running in 32-bit mode:

~# for p in $(ps -p 2 --ppid 2 --deselect -o pid=); do grep -qE '^.{8}[^-]' /proc/$p/maps || echo $p; done | xargs ps f -o pid,cmd -p
  PID CMD
 3359 /system/bin/mediaserver
 3358 /system/bin/cameraserver
 3357 /system/bin/audioserver
 3356 zygote
 5081  \_ webview_zygote
20824  |   \_ com.android.webview:sandboxed_process0
18609  \_ it.colucciweb.vpnclient
 3354 /vendor/bin/hw/android.hardware.audio@2.0-service
 2665 /vendor/bin/hw/android.hardware.camera.provider@2.4-service
 2335 /vendor/bin/mm-qcamera-daemon
 2278 /vendor/bin/wifidisplayhalservice
 2277 media.codec hw/android.hardware.media.omx@1.0-service
 2244 /system/bin/drmserver
  777 /vendor/bin/hw/android.hardware.drm@1.1-service.widevine
  773 /vendor/bin/hw/android.hardware.cas@1.0-service


Out of these only one is a VPN app, rest are OS processes.

CONCLUSION

So it's clear that even if core OS is 64-bit, there are possibly large number of processes running as 32-bit, though each process and its linked libraries have to be be homogeneous; either 32 or 64-bit. And there's no constraint if the OS doesn't include 64-bit code at all, but you won't find many instances because OEMs ship 64-bit devices with 64-bit binary code. It's more about business than performance; that's what a user pays for. However exceptions do exist where business isn't that much involved: Raspberry Pi 3 has 64-bit CPU, but 32-bit Raspbian OS. Android phones examples from past include Galaxy E5, LG G Stylo, Moto G5 Plus, Lenovo A6000 Plus and MT6735 devices. But now Android is moving towards 64-bit and some devices may drop 32-bit support gradually.


  Does the same case happens for Android? Is there a 32-bit and a 64-bit version for the same 64-bit device?


Yes, take example of Moto G4 Plus.



RELATED: Android apps for “armeabi-v7a” and “x86” architecture: SoC vs. Processor vs. ABI