Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Unicorn 2 regression: ARM mode transition from UND32 to SVC32 corrupts sp_svc #1494

Closed
gerph opened this issue Nov 21, 2021 · 2 comments
Closed

Comments

@gerph
Copy link
Contributor

gerph commented Nov 21, 2021

I have a large operating system implementation using Unicorn 1. I am looking to upgrade, but although it appears to start correctly for simple things, I have found a problem with the support for UND32 -> SVC32 transitions (possibly others, but this fails early in module initialisation whilst the floating point emulator code is initialising).

Source environment

I built unicorn on the dev branch at sha 87a391d549a339b5d8109c10c6b16bd95130a923.
This has been tested on OSX system on Intel hardware (10.14.6)

Failure mode

The failure appears to be that on return from UND32 to SVC32 (via a MOVS pc, lr, with SPSR set to the SVC32 mode) the mode changes, but the stack pointer is corrupted - it becomes 0.

What are we testing here?

We want to know that the change back from UND32 to SVC32 restores the banked
registers.

Our state at the start of execution:

  • CPSR = UND32 mode
  • SPSR = SVC32 mode
  • sp_und = 0xDEAD0000
  • sp_svc = 0x12345678
  • lr-> code to execute in SVC mode (a MVN r0,#0 as a dummy instruction)
  • pc-> code to execute in UND mode (a MVN dummy instruction, then MOVS pc, lr)

What we observe is that:

  • the first two instructions execute fine in UND32 mode
  • we return to SVC32 mode after the MOVS pc, lr instruction
  • we begin execution at 0x12000, the expected address
  • BUT sp has been set to 0.
  • This is a serious fault and makes handling of undefined instructions impossible.

Why is this a problem?

  • My OS implementation (https://pyromaniac.riscos.online/) is reliant on the UND mode
    behaviour in order to emulate FPA instructions. This worked under Unicorn 1,
    but does not under Unicorn 2 as built here.

Test code

Test code which exhibits this problem here:

#!/usr/bin/env python
# Sample code for ARM of Unicorn. Nguyen Anh Quynh <aquynh@gmail.com>
# Python sample ported by Loi Anh Tuan <loianhtuan@gmail.com>

from __future__ import print_function
from unicorn import *
from unicorn.arm_const import *


# code to be emulated
ARM_CODE_UND   = [b"\x00\x00\xe0\xe3", # MVN r0, #0
                  b"\x0e\xf0\xb0\xe1", # MOVS pc, lr
                  b"\x00\x00\xe0\xe3", # MVN r0, #0
                 ]
ARM_CODE_SVC   = [b"\x00\x00\xe0\xe3", # MVN r0, #0
                 ]

# memory address where emulation starts
ADDRESS_UND  = 0x10000
ADDRESS_SVC  = 0x12000

MAPPED_UND = 4096
MAPPED_SVC = 4096


# callback for tracing instructions
def hook_code(uc, address, size, user_data):
    print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" %(address, size))
    print("    CPSR = 0x{:08x}".format(uc.reg_read(UC_ARM_REG_CPSR)))
    print("    SPSR = 0x{:08x}".format(uc.reg_read(UC_ARM_REG_SPSR)))
    print("    SP = 0x{:08x}".format(uc.reg_read(UC_ARM_REG_R13)))
    print("    PC = 0x{:08x}".format(uc.reg_read(UC_ARM_REG_R15)))


# Test ARM
def test_arm():
    print("Testing under Unicorn : {!r}".format(uc_version()))
    print("Header version: {!r}".format((UC_VERSION_MAJOR, UC_VERSION_MINOR, UC_VERSION_EXTRA)))

    print("Emulate ARM code")
    try:
        # Initialize emulator in ARM mode
        mu = Uc(UC_ARCH_ARM, UC_MODE_ARM)

        # map some memory for each block
        mu.mem_map(ADDRESS_UND, MAPPED_UND)
        mu.mem_map(ADDRESS_SVC, MAPPED_SVC)

        # write machine code to be emulated to memory
        code = b''.join(ARM_CODE_UND)
        mu.mem_write(ADDRESS_UND, code)

        code = b''.join(ARM_CODE_SVC)
        mu.mem_write(ADDRESS_SVC, code)

        # initialize machine registers in different modes
        mu.reg_write(UC_ARM_REG_CPSR, 0x40000093)   # Current mode = SVC32 mode
        mu.reg_write(UC_ARM_REG_R13, 0x12345678)    # SVC stack value

        mu.reg_write(UC_ARM_REG_CPSR, 0x4000009b)   # Current mode = UND32 mode
        mu.reg_write(UC_ARM_REG_SPSR, 0x40000093)   # Saved mode = SVC32 mode
        mu.reg_write(UC_ARM_REG_R13, 0xDEAD0000)    # UND stack value
        mu.reg_write(UC_ARM_REG_R14, ADDRESS_SVC)   # return address for the UND vector

        # tracing one instruction at ADDRESS with customized callback
        mu.hook_add(UC_HOOK_CODE, hook_code)

        # What are we testing here?
        # We want to know that the change back from UND32 to SVC32 restores the banked
        # registers.

        # Our state at the start of execution:
        #   CPSR = UND32 mode
        #   SPSR = SVC32 mode
        #   sp_und = &DEAD0000
        #   sp_svc = &12345678
        #   lr-> code to execute in SVC mode (a MVN r0,#0 as a dummy instruction)
        #   pc-> code to execute in UND mode (a MVN dummy instruction, then MOVS pc, lr)

        # What we observe is that:
        #   the first two instructions execute fine in UND32 mode
        #   we return to SVC32 mode after the MOVS pc, lr instruction
        #   we begin execution at 0x12000, the expected address
        #   BUT sp has been set to 0.
        #   This is a serious fault and makes handling of undefined instructions impossible.

        # Why is this a problem?
        #   My OS implementation (pyromaniac.riscos.online) is reliant on the UND mode
        #   behaviour in order to emulate FPA instructions. This worked under Unicorn 1,
        #   but does not under Unicorn 2.

        # emulate machine code in infinite time
        mu.emu_start(ADDRESS_UND, ADDRESS_SVC + len(code))

        # now print out some registers
        print(">>> Emulation done. Below is the CPU context")

        print("    CPSR = 0x{:08x}".format(mu.reg_read(UC_ARM_REG_CPSR)))
        print("    SPSR = 0x{:08x}".format(mu.reg_read(UC_ARM_REG_SPSR)))
        print("    SP = 0x{:08x}".format(mu.reg_read(UC_ARM_REG_R13)))
        print("    PC = 0x{:08x}".format(mu.reg_read(UC_ARM_REG_R15)))

    except UcError as e:
        print("ERROR: %s" % e)



if __name__ == '__main__':
    test_arm()

Failing output (Unicorn 2)

This produces the following output on Unicorn 2 (failing output):

Testing under Unicorn : (2, 0, 262656L)
Header version: (2, 0, 0)
Emulate ARM code
>>> Tracing instruction at 0x10000, instruction size = 0x4
    CPSR = 0x4000009b
    SPSR = 0x40000093
    SP = 0xdead0000
    PC = 0x00010000
>>> Tracing instruction at 0x10004, instruction size = 0x4
    CPSR = 0x4000009b
    SPSR = 0x40000093
    SP = 0xdead0000
    PC = 0x00010004
>>> Tracing instruction at 0x12000, instruction size = 0x4
    CPSR = 0x40000093
    SPSR = 0x00000000
    SP = 0x00000000
    PC = 0x00012000
>>> Emulation done. Below is the CPU context
    CPSR = 0x40000093
    SPSR = 0x00000000
    SP = 0x00000000
    PC = 0x00012004

The 3rd traced instruction should have SP =0x12345678

Successful output (Unicorn 1)

Testing under Unicorn : (1, 0, 256L)
Header version: (1, 0, 2)
Emulate ARM code
>>> Tracing instruction at 0x10000, instruction size = 0x4
    CPSR = 0x4000009b
    SPSR = 0x40000093
    SP = 0xdead0000
    PC = 0x00010000
>>> Tracing instruction at 0x10004, instruction size = 0x4
    CPSR = 0x4000009b
    SPSR = 0x40000093
    SP = 0xdead0000
    PC = 0x00010004
>>> Tracing instruction at 0x12000, instruction size = 0x4
    CPSR = 0x40000093
    SPSR = 0x00000000
    SP = 0x12345678
    PC = 0x00012000
>>> Emulation done. Below is the CPU context
    CPSR = 0x40000093
    SPSR = 0x00000000
    SP = 0x12345678
    PC = 0x00012000
@wtdcode
Copy link
Member

wtdcode commented Nov 21, 2021

Hello, glad to see you are using Unicorn to build a large system!

Thanks for your PoC and I would have a look ASAP.

@wtdcode
Copy link
Member

wtdcode commented Nov 24, 2021

Fixed in 221cde1

# for free to join this conversation on GitHub. Already have an account? # to comment
Projects
None yet
Development

No branches or pull requests

2 participants