Skip to content

ROM Analysis in Basilisk II Emulation

Ricky Zhang edited this page Oct 10, 2017 · 22 revisions

Table of Contents

Assumptions

The analysis below is based on the study of the Performa old world 32bit clean ROM (with MD5 hash af343f3f1362bf29cefd630687efaa25). I found that this Performa 630 ROM works great for System 7 and Mac OS 8.1.

Disassemble original 68k Mac ROM

Assuming you get a Mac ROM binary file for BII from somewhere, the next step is to poke around it. A ROM file is a collection of data and M68K machine code which performs hardware test at boot time, provides low level common routines such A-Trap and etc.

For emulation purposes, BII patches the original ROM so that it can bypass hardware test and complete overwrite Macintosh hardware drive with new emulated driver. I will demonstrate disassembling this Mac ROM with cxmon and radare2.

Disassemble with cxmon

Build cxmon

$ cd macemu/cxmon
$ ./bootstrap
$ ./configure
$ make
$ sudo make install

Use cxmon

The command below disassemble ROM file and dump the text into an external file.

cxmon -m ‘[ 0 “PERFORMA.ROM”’ ‘d68 0 fffff’ > PERFORMA.ROM.DISAM

You can also run cxmon in standalone interactive mode or even enable --with-mon option in BII build to debug at runtime. Please refer to cxmon help

NOTE: After reading the disassemble code, cxmon didn't disassemble correctly in some cases. This is due to the fact that ROM mixed with data and code. For example, read ROM starting from 0x2a offset. The machine code 0x46fc2700 should be at address $0000008c. The correct assembly should be move #?2700, sr, instead of ori.b #$fc,d0 and move.l d0,-(a3).

    11 000000000000002a: 4efa 0060             jmp ($0000008c,pc)
...
    37 000000000000008a: 0000 46fc             ori.b   #$fc,d0
    38 000000000000008e: 2700                  move.l  d0,-(a3)
    39 0000000000000090: 4dfa 000a             lea ($0000009c,pc),a6

Disassemble with radare2

Build radare2

Build radare2 from git repo. You also need to build acr.

Use radare2

The documentation of radare2 is long. TLDR; Here is an example of using it:

[Ricky@xps ROM.Disas]$ radare2 PERFORMA.ROM 
 -- Switch between print modes using the 'p' and 'P' keys in visual mode
[0x00000000]> e asm.arch=m68k
[0x00000000]> pd 10 arch=m68k
            0x00000000      066842140000   addi.w 0x4214, 0x0(a0)
            0x00000006      002a067c4efa   ori.b 0x7c, 0x4efa(a2)
        |   0x0000000c      00804efa007c   ori.l 0x4efa007c, d0
        |   0x00000012      32f10100       move.w (a1, d0.w), (a1)+
        |   0x00000016      00000044       ori.b 0x44, d0
        |   0x0000001a      0007ec10       ori.b 0x10, d7
        |   0x0000001e      4efa1220       jmp 0x1220(pc)
        |   0x00000022      000d2da0       invalid
        |   0x00000026      4efa22e8       jmp 0x22e8(pc)
        |   0x0000002a      4efa0060       jmp 0x60(pc)

NOTE: radare2 has the same issue as cxmon. This comes to the dead end. To read ROM disassemble code, you need to tag code segment manually. [Update Aug 25, 2017] In MPW GM, ROMMap folder provides a long list of Macintosh ROMs that segment A-Trap. However, I can't find Performa 640 ROM mapping there.

Mac ROM in real Macintosh hardware

In Apple Mac IIci and Mac Quadra 900 Developer Note, it specified that ROM must be in a fixed address space. Because ROM machine code is written as position independent code. In reality, its location in memory is relocatable. That’s why BII can load ROM to whatever memory address in guest OS without breaking its logic.

So far I haven’t found an automatic way to disassemble Performa ROM without human tagging code segment. I can’t easily get a full picture of what Performa ROM contains. But reading BII ROM patches, Macintosh ToolBox trap and Macintosh OS trap, I think I need to have basic understanding of using illegal instruction exception technique in M68k CPU.

Illegal Instructions Exception in M68k CPU

M68k CPU suspends execution instruction flow if it encounters an illegal instruction. An illegal instruction is an instruction that contains any bit pattern in its first word that does not correspond to the bit pattern of the first word of a valid M68k instruction or is a MOVEC instruction with an undefined register specification field in the first extension word.

When an illegal instruction exception happens, there are four steps in processing exception [1]:

  1. The processor makes an internal copy of the status register. Then it sets the S bit, changing to the supervisor privilege level. Next, it inhibits tracing of the exception handler by clearing the T1 and T0 bits.
  2. The processor determines the vector number of the exception.
  3. The processor saves the current processor context and creates an exception stack frame on the active supervisor stack and fills it with context information appropriate for the type of exception.
  4. The processor multiples the vector number of the exception by 4 to get the offset of vector table. Vector Base Register VBR points to the address of vector table. So add the offset with VBR to get the address of the exception handler routine. Then, load Program Counter PC with the look-up address and resume CPU execution.

The following is exception vector number table:

Exception_Vector_Table_1

Exception_Vector_Table_2

Macintosh OS reserves the first word 1010 (0xA) unimplemented instruction to implement so called A-Trap to provide ToolBox and OS API. Its vector number is 0xA

Basilisk II also uses undefined instruction with prefix 0x71xx to patch Mac ROM and implement its emulated drivers. However, the implementation of illegal 0x71xx instruction in BII bypasses M68k CPU illegal instruction exception. We will discuss this in next section.

Mac ROM in Basilisk II emulation

Initialization

BII loads the unmodified ROM file from disk into memory at RomBaseHost address. Depending on addressing mode, ROM location (i.e. ROMBaseMac) varies from Macintosh guest OS point of view.

0067 bool Init680x0(void)
0068 {
0069 #if REAL_ADDRESSING
0070     // Mac address space = host address space
0071     RAMBaseMac = (uintptr)RAMBaseHost;
0072     ROMBaseMac = (uintptr)ROMBaseHost;
0073 #elif DIRECT_ADDRESSING
0074     // Mac address space = host address space minus constant offset (MEMBaseDiff)
0075     // NOTE: MEMBaseDiff is set up in main_unix.cpp/main()
0076     RAMBaseMac = 0;
0077     ROMBaseMac = Host2MacAddr(ROMBaseHost);
0078 #else
0079     // Initialize UAE memory banks
0080     RAMBaseMac = 0;
0081     switch (ROMVersion) {
0082         case ROM_VERSION_64K:
0083         case ROM_VERSION_PLUS:
0084         case ROM_VERSION_CLASSIC:
0085             ROMBaseMac = 0x00400000;
0086             break;
0087         case ROM_VERSION_II:
0088             ROMBaseMac = 0x00a00000;
0089             break;
0090         case ROM_VERSION_32:
0091             ROMBaseMac = 0x40800000;
0092             break;
0093         default:
0094             return false;
0095     }
0096     memory_init();
0097 #endif
...

As pointed out above, ROM is written in position independent manner. Thus, the starting address of ROM is relocatable in memory. You may wonder how guest OS knows the location of ROM at runtime. After ROM startup code perform memory test, it initializes a list of global variables in the fixed memory address. You can find the whole list of global variables and its memory location from Part 1. Global Variables in Memory Address Order .

To verify this, I did an experiment in BII. From the list, we know that global variable ROMBase is in address 0x2AE. So trigger a segfault to make BII runs into cxmon. We will poke around the guest OS memory from there.

First, we shows the content at address 0x2AE.

[00000000ff3f32ff]-> m 02ae
00000000000002ae: 3ff00000 00002000 000031a0 0008cbe0  '?..... ...1.....'

Because I runs in Intel little endian CPU. 0x3ff00000 should be 0x3ff00000 in big endian. Here is the patched ROM in memory with respect to guest OS address space.

[0000000f03f00100]-> m 3ff00000
000000003ff00000: 06684214 0000002a 067c4efa 00804efa  '.hB....*.|N...N.'
000000003ff00010: 007c32f1 01000000 00440007 ec104efa  '.|2......D....N.'
000000003ff00020: 1220000d 2da04efa 22e84efa 00600000  '. ..-.N.".N..`..'
000000003ff00030: 0101ede8 01251b34 01023267 01214c0a  '.....%.4..2g.!L.'

Now, compare above with the PERFORMA original ROM file.

$ hexdump PERFORMA.ROM | head -n 4
0000000 6806 1442 0000 2a00 7c06 fa4e 8000 fa4e
0000010 7c00 f132 0001 0000 4400 0700 10ec fa4e
0000020 2012 0d00 a02d fa4e e822 fa4e 6000 0000
0000030 0101 e8ed 2501 341b 0201 6732 2101 0a4c

Again, because hexdump respects Intel platform little endianness. After do some word swapping, it matches the exact ROM contents from cxmon.

Patching

After BII loads ROM from file into memory, it patches ROM in PatchRom() function from file src/rom_patches.cpp.

Patching Macintosh ROM is a daunting task:

  1. You need to know M68K assembly and how to write Macintosh driver.
  2. You need to find the right place to patch. This requires you have some basic understanding of ROM logic.
  3. Because the size of ROM is fixed, there is very limited room left to extend new functionality.

To tackle the first obstacle, you need to read a lot of documentation. Motorola 68K CPU programming reference, Inside Macintosh Volume I to VI, Apple technical note from archive.org are helpful resources.

For the second one, you may get help from annotated disassembled ROM in Mini vMac tool FDisasm. So far there are only 64K, 128K and 256K ROM formatting information available. But it is far better than reading ROM by cxmon or radare2. It helped me find the right spot to install emulated hard drive driver in 24-bit ROM.

The last puzzle is solved by the technique of adding undefined M68 instruction 0x71xx in BII emulator. BII emulator patches ROM driver with a set of new instructions. It replaces original driver installation step with BII internal 0x71xx instruction trap. Like Apple A-trap, those undefined 0x71xx instruction calling the core driver logic, which is implemented in C++ inside BII. In this way, it can easily squeeze new functionality in the limited ROM and also implement them in the high level language.

For technical details, this commit shows a simple example of adding a new instruction that suspends BII CPU and traps into cxmon.

Execution

Emulation starts at relative address 0x2a i.e (MacROMBaseMac + 0x2a) in the patched ROM. TODO add more

Bibliography

1.MC68030 Datasheet