-1

I mean: This build(including both x86 and ARM architecture drivers) can run on an x86 computer, and can run on an other ARM device.

I know that right now, there is no such CPU that supports both instruction sets. But in theory, will it come true? When powered up, the CPU can check which instruction sets the program is in?

little白
  • 203
  • 2
  • 4
  • 10
  • 1
    Your question is confusing/confused. Your first paragraph asks: "*Can we build a kernel that runs on x86 and ARM?*". Your second paragraph asks a completely different question: "*Can we build a CPU that figures out the ISA used by a program, and change its behavior accordingly?*". – ArjunShankar Aug 03 '12 at 14:18
  • Thanks. In the second paragraph, I proposed a method that can realize that a kernel run both on two architecture. – little白 Aug 07 '12 at 06:53

5 Answers5

4

It is possible.

In addition to dave and other posters' answers, I wrote a python script that will produce the code trickery that will run on both x86 and ARM which will take different paths depending on which is booting.

I'm not familiar with x86 so forgive me if this doesn't work.

import sys
import struct
import math

armClobberReg = 0
armShift = 31

if (armClobberReg>15):
    sys.stderr.write("Error! ARM clobber register is invalid\n")
    sys.exit(-1)
if (armClobberReg == 15):
    sys.stderr.write("Warning: r15 will be clobbered on ARM\n")
if (armShift & 1 != 1 or armShift > 31):
    sys.stderr.write("Warning! ARM shift is invalid. Using 31 instead\n")
    armShift = 31

jmpOffset = (armClobberReg<<4)|(armShift>>1)
jmpAndEqNopInstruction = struct.pack("<BBBBI",0xE9, jmpOffset,0,0,0xE1A00000)
armNoOp = struct.pack("<I",0xE1A00000)
x86NoOp = struct.pack("c","\x90")

asmOut = jmpAndEqNopInstruction

asmArmTxtOut = "{0:08x}: andeq r{1}, r0, r9, ror #{2}\n".format(0, armClobberReg, armShift)
asmArmTxtOut+= "{0:08x}: nop\n".format(4)

asmx86TxtOut = "{0:08x}: jmp {1:x}\n".format(0, jmpOffset)
asmx86TxtOut+= "          ... (ARM instructions)\n"

i = math.floor((jmpOffset + 5)/4) - 2
armPc = 8
while i>0:
    asmOut += armNoOp
    asmArmTxtOut+= "{0:08x}: nop <replace with your arm instructions>\n".format(armPc)
    armPc+=4
    i-=1

asmOut = asmOut.ljust(jmpOffset + 5, chr(0xff))
asmOut += x86NoOp

asmx86TxtOut += "{0:08x}: nop <replace with your x86 instructions>\n".format(jmpOffset + 5)
asmArmTxtOut += "          ... (x86 instructions)\n"


sys.stderr.write("x86 disassembly: \n"+asmx86TxtOut+"\n")
sys.stderr.write("ARM disassembly: \n"+asmArmTxtOut+"\n")

sys.stderr.write("ARM code length limited to {0} instructions\n".format(int(math.floor((jmpOffset + 5)/4) - 2)))

sys.stdout.write(asmOut)
sys.stderr.write("Wrote opcodes to stdout\n\n")

On ARM, the first and second instruction is interpreted as an andeq and nop instruction while on x86, it is interpreted as a jmp instruction.

  • ARM disassembly:

    ARM disassembly result

  • x86 disassembly:

    x86 disassembly result

You can append your own opcodes in it that will branch to the appropriate kernel.

Community
  • 1
  • 1
tangrs
  • 9,709
  • 1
  • 38
  • 53
2

Let's assume that it can be done. What would need to be done?

Well, you'd need the whole kernel compiled twice, once using x86 and once using ARM opcodes (assembler after it's been assembled). Okay then, all we need is a small program that queries which CPU we are on and then branches to either the x86 version or the ARM version.

Oops, there's a boot strapping problem. Which of the two opcodes are you going to use in order to figure out which version to use? ARM or x86 - remember it needs to run on both.

It might be possible. Perhaps an encoding of a branch instruction is a nop or something on an ARM. So now I might be able to have the first instruction be different for both x86 and ARM - one of them branching a short distance and I'd now know that it's x86 and jump from there in to my x86 kernel. And the other wouldn't take the branch and would fall through to the code below which could then branch to the ARM version (or vice versa).

That would be an awesome code trick.

But why would you want to do that? You'd have half the kernel in memory not being used (perhaps you can swap it out to disk). And you may as well just compile for the right target from the start. (Also, I'm not sure of any tool that would let you compile the two kernels and put them in the one image, but it could be done if you hacked your own linker).

dave
  • 4,812
  • 4
  • 25
  • 38
  • 1
    See [Is it possible to detect the CPU architecture from machine code?](https://stackoverflow.com/a/38056588) for polyglot ARM / x86 machine code that branches to different locations depending on the ISA, with relative branches to nearby addresses. – Peter Cordes Oct 31 '18 at 22:42
  • re: what to do with the other half of memory? The x86 kernel would simply mark the ARM kernel memory regions as free, and use it for dynamic allocation or zero it and use it as its BSS. And vice versa. Modern Linux marks some functions as "init", and groups them together in an init section it can free after they've served their purpose. (Freeing a block for the kernel means adding it to the free list for an allocator.) – Peter Cordes Oct 31 '18 at 22:46
2

This is mainly a question of support in the bootloader, and what the OS does once loaded. It's not only theoretically possible, OSX kernels (/mach_kernel) for versions 10.6.x-10.7.x come as universal i386/x86_64 binaries:

$ file /Volumes/KernelDebugKit/mach_kernel
/Volumes/KernelDebugKit/mach_kernel: Mach-O universal binary with 2 architectures
/Volumes/KernelDebugKit/mach_kernel (for architecture x86_64):  Mach-O 64-bit executable x86_64
/Volumes/KernelDebugKit/mach_kernel (for architecture i386):    Mach-O executable i386

The 10.5.x kernel even came as a i386/PowerPC universal binary:

$file /Volumes/KernelDebugKit/mach_kernel
/Volumes/KernelDebugKit/mach_kernel: Mach-O universal binary with 2 architectures
/Volumes/KernelDebugKit/mach_kernel (for architecture i386):    Mach-O executable i386
/Volumes/KernelDebugKit/mach_kernel (for architecture ppc): Mach-O executable ppc

The bootloader (Apple's EFI firmware implementation (x86) or OpenFirmware (ppc) in the case of OSX) will need to support the "fat" binary format and be able to decide on and run the correct one. Of course, once the kernel is loaded, it needs the entire support infrastructure of the rest of the operating system to be available in a compatible format in order to do anything useful.

Note that older versions of OSX even shipped with "Rosetta", a runtime binary translation system for running PowerPC binaries on an x86/x86-64 CPU.

To be clear, it's completely irrelevant that the x86 and ppc versions of the kernel are stored in the same file.

pmdj
  • 22,018
  • 3
  • 52
  • 103
1

I don't think so, there is the possibility of writing a kernel in both instruction sets, and having a special startup code that executes differently on both architectures that fires up the appropriate kernel for the architechture.

Jens Björnhager
  • 5,632
  • 3
  • 27
  • 47
  • Thats pretty much how OS X does it, the kernel includes the code for all supported architectures and there is some special bootstrapping code that gets everything up and running for the current architecture. – JustSid Aug 03 '12 at 09:54
0

Well in my opinion this will not come true. My reasons are: - It's easier to compile two versions of a program and during installation it could decide wheter it's on an ARM or x86 platform.

  • If you're looking at the 32 Bit commands. A lot of them are used and you would need to double the instruction set which would decrease the efficency of your CPU. And if you're unlucky a 32Bit instruction set would satisfy your needs anymore so you would have to change to a 33Bit and this would be extremly awkward.

  • really I don't see why you would want to do this. It just causes inefficiency

Lukas Häfliger
  • 526
  • 4
  • 17