Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Unicorn 2 regression: Something is wrong with MSR not changing mode? #1531

Closed
gerph opened this issue Jan 11, 2022 · 21 comments
Closed

Unicorn 2 regression: Something is wrong with MSR not changing mode? #1531

gerph opened this issue Jan 11, 2022 · 21 comments

Comments

@gerph
Copy link
Contributor

gerph commented Jan 11, 2022

Once again, thanks for the changes up to now to address mode changes in ARM. I think we're very close to things working as expected. The reason I say this is that I cannot reproduce the failure case for the bug I'm having with smaller code. Every time I tried creating code that looks like it does the same thing in a shorter section of code, the emulation worked fine, and so I decided to go about reproducing this in a different way - heavy handed, but which actually shows the problem in a way that you'll be able to reproduce, even if it's large.

Sorry the title isn't specific - there's something up with the MSR and it doesn't change mode in this specific circumstance.

Background

I have an implementation of the RISC OS operating system written in Python. It uses Unicorn to provide the ARM execution environment. The problem that is being encountered occurs when a C-based program is exiting and returning back to user space. This happens long after the system has been initialised and after the program has almost completed running - I have a small tool which causes the problem, but it's still a lot of ARM code to be executed.

The instruction that appears to be failing is msr cpsr_c, r6, where r6 is 0x60000010. In Unicorn 2 it fails to be executed, or the execution is treating the it as a different instruction like andeq r0,r0,r0 - either way, the mode is not changed. In Unicorn 1 this instruction causes us to return to USR32, as the value in r6 indicates

Lots of mode changes are performed within the course of getting to this point in the OS, and I cannot see a way that the environment is different in the this case.

Because so much of the OS has been executed to this point, and I was unable to create a small repro case, the solution I chose was to create a new class to interpose between Unicorn and the OS, which logs every method and variable access. The log is python code - and for simple things, it means that you can run the log and it will behave like the system that ran it, reproducing any behaviour that the Unicorn system had originally. In my case there were a few minor tweaks but the repro case now runs correctly on Unicorn 1, but fails on Unicorn 2, just like the real OS does.

The repro case is 52014 lines of python, coming in at 4MB - sorry, it's large, but as I said I couldn't reproduce it by hand crafted cide, so this in-situ recording is the best I can do, and it ought to reproduce for you.

Source environment

I built unicorn on the dev branch at sha 36afa10.
This is the most recent version this afternoon, after the fixes for mode changes.
This has been tested on OSX system on Intel hardware (10.14.6)

Failure mode

The repro-case appears to work identically up to the point marked:

# <<<<< THIS IS WHERE BEHAVIOUR BEGINS TO DIFFER >>>>>

To make it easy to see what's going on from that point, some debug has been added which dumps the registers, adds a hook which disassembles the code being executed, and then dumps the registers again - the second set of registers, and the code path taken differs between Unicorn 1 and Unicorn 2.

The code being executed at the failure point is:

0x07017918: movvc    r1, r0
0x0701791c: bvc      #0x7017934
0x07017934: ldm      sp!, {lr}
0x07017938: msr      cpsr_c, r6
0x0701793c: pop      {lr}
0x07017940: msr      apsr_nzcvq, lr
0x07017944: pop      {r0, r2, r3, r4, r5, r6, pc}
0x07017834: mov      r2, #0
0x07017838: add      r4, pc, #0x7c
0x0701783c: mov      r5, #0
0x07017840: mov      r6, #0
0x07017844: mov      r7, #0
0x07017848: svc      #0x61506

The difference is the msr cpsr_c, r6 instruction, which appears to have no effect on the Unicorn 2 execution, which then leaves us in SVC32 mode, rather than USR32, and thus the pop {lr} removes a value from the wrong stack, and more bad things happen after that - finally resulting in hitting the ARM exception vectors because the pop at 0x07017944 overflows the SVC32 stack (I think). In any case the point of divergence is the instruction at 0x07017938, I believe.

It is possible that something that the code I'm using is doing is wrong, but I cannot see it.

Test code

The repro case should be attached to this bug as a .py.gz file.:

ucbroken.py.gz

The part of code where execution differs is at line 51905, which looks like this:

# <<<<< THIS IS WHERE BEHAVIOUR BEGINS TO DIFFER >>>>>
dump_registers(uc2)
uc2.hook_add(unicorn.UC_HOOK_CODE, hook_code)
uc2.emu_start(value_40903, 0, timeout=0)  # None
dump_registers(uc2)

Failing output (Unicorn 2)

On Unicorn 2 the following output is produced:

(venv) charles@laputa ~/projects/RO/pyromaniac (unicorn-object-debugging↑3)> python ucbroken.py
Testing under Unicorn : (2, 0, 33554437L)
Header version: (2, 0, 5)
Registers: 
   r0 : 0x070005ac   r1 : 0x070178a4   r2 : 0x00000000   r3 : 0x00000010
   r4 : 0x00000000   r5 : 0x00000000   r6 : 0x60000010   r7 : 0x00000000
   r8 : 0x00800e06   r9 : 0x0000a21c  r10 : 0x0000b2b0  r11 : 0x0000c04c
  r12 : 0xf8ff2660   sp : 0x04107fdc   lr : 0x00000000   pc : 0x07017918
  CPSR = 0x60000013
  SPSR = 0x00000013
0x07017918: movvc    r1, r0
0x0701791c: bvc      #0x7017934
0x07017934: ldm      sp!, {lr}
0x07017938: msr      cpsr_c, r6
0x0701793c: pop      {lr}
0x07017940: msr      apsr_nzcvq, lr
0x07017944: pop      {r0, r2, r3, r4, r5, r6, pc}
Registers: 
   r0 : 0x2300000d   r1 : 0x070005ac   r2 : 0x00000000   r3 : 0x00000000
   r4 : 0x00000000   r5 : 0x00000000   r6 : 0x00000013   r7 : 0x00000000
   r8 : 0x00800e06   r9 : 0x0000a21c  r10 : 0x0000b2b0  r11 : 0x0000c04c
  r12 : 0xf8ff2660   sp : 0x04108000   lr : 0xeffeed05   pc : 0x00000000
  CPSR = 0xe8000013
  SPSR = 0x00000013
0xffff0000: ldr      pc, [pc, #0x18]
0xffff0040: svc      #0xfeed20
0x03800074: svc      #0xfeed07

Observe that we remain in SVC32 mode at the point of the exception (the pop), and that the value of lr is garbage (because it has been pulled from the SVC32 stack). The instruction at 0x07017938 should have selected USR32 mode (because r6 = 0x60000010).

The code executed after the second register dump is irrelevant as we're now trying to handle the exception caused by the unexpected behaviour.

Successful output (Unicorn 1)

(venv) charles@Laputa ~/projects/RO/pyromaniac (unicorn-object-debugging↑3)> python ucbroken.py
Testing under Unicorn : (1, 0, 256L)
Header version: (1, 0, 2)
Registers: 
   r0 : 0x070005ac   r1 : 0x070178a4   r2 : 0x00000000   r3 : 0x00000010
   r4 : 0x00000000   r5 : 0x00000000   r6 : 0x60000010   r7 : 0x00000000
   r8 : 0x00800e06   r9 : 0x0000a21c  r10 : 0x0000b2b0  r11 : 0x0000c04c
  r12 : 0xf8ff2660   sp : 0x04107fdc   lr : 0x00000000   pc : 0x07017918
  CPSR = 0x60000013
  SPSR = 0x00000013
0x07017918: movvc    r1, r0
0x0701791c: bvc      #0x7017934
0x07017934: ldm      sp!, {lr}
0x07017938: msr      cpsr_c, r6
0x0701793c: pop      {lr}
0x07017940: msr      apsr_nzcvq, lr
0x07017944: pop      {r0, r2, r3, r4, r5, r6, pc}
0x07017834: mov      r2, #0
0x07017838: add      r4, pc, #0x7c
0x0701783c: mov      r5, #0
0x07017840: mov      r6, #0
0x07017844: mov      r7, #0
0x07017848: svc      #0x61506
Registers: 
   r0 : 0x07015148   r1 : 0x070005ac   r2 : 0x00000000   r3 : 0x00000100
   r4 : 0x070178bc   r5 : 0x00000000   r6 : 0x00000000   r7 : 0x00000000
   r8 : 0x00800e06   r9 : 0x0000a21c  r10 : 0x0000b2b0  r11 : 0x0000c04c
  r12 : 0xf8ff2660   sp : 0x0000bfd4   lr : 0x60000010   pc : 0x0701784c
  CPSR = 0x60000010
  SPSR = 0x00000000
0xffff0000: ldr      pc, [pc, #0x18]
0xffff0040: svc      #0xfeed20
0x03800074: svc      #0xfeed07

In this case we can see that we're executing more code - the pop instruction is happy, because we're in USR32 mode, and we continue until we hit an svc instruction. At that point we can see the registers and that we definitely are in USR32 (0x60000010).

Final words

I'm not sure what else to suggest for reproducing this other than using the code as it stands - I assume that some internal state is different between the Unicorn 1 and Unicorn 2 systems. Hopefully this will be able to give you some clues as to what's going wrong - I've been unable to narrow it further.

I really hope you can help find the bug - whether it's in how I'm using Unicorn or in Unicorn 2 - because it's very close to making the OS work on Unicorn 2 now.

@wtdcode
Copy link
Member

wtdcode commented Jan 11, 2022

I decide to label this as enhancement since we indeed need some extra debug output (logging) to help developers address the problem in a big script.

@gerph
Copy link
Contributor Author

gerph commented Jan 11, 2022

Understood; sorry I couldn't make it smaller. I might have a see if there's anything I can do to isolate it better anyhow.

I had wondered, when I was doing the prior changes to add debug to the MSR on my own branch, whether it would be possible to introduce a control interface to enable the back end qemu debug. Then it could just be enabled around the bit that was interesting. But I don't really have enough understanding of the innards for that, I think.

@wtdcode
Copy link
Member

wtdcode commented Jan 11, 2022

Understood; sorry I couldn't make it smaller. I might have a see if there's anything I can do to isolate it better anyhow.

I had wondered, when I was doing the prior changes to add debug to the MSR on my own branch, whether it would be possible to introduce a control interface to enable the back end qemu debug. Then it could just be enabled around the bit that was interesting. But I don't really have enough understanding of the innards for that, I think.

There is some debugging output if you set UNICORN_DEBUG env to 1, but I haven't finished and documented this development feature yet.

@gerph
Copy link
Contributor Author

gerph commented Jan 11, 2022

The UNICORN_DEBUG looks like it's related to tcg operations? I wasn't sure that was useful.

I have made some changes to my debug branch which has extra debug to help with isolating the mode problems that I encountered. The debug branch is here: https://github.com/gerph/unicorn/tree/cjf-debug-privileged-mode-problems and the change that adds the new runtime controls is here: gerph@38fb9b5 (not sure why the PPC things changed- I haven't touched that area of the code).

Basically, I introduced a new ctl_set_debug_mask() function in the Python side, which passes to a UC_CTL_DEBUG_MASK control in the C side, which calls to qemu_set_loglevel or qemu_get_loglevel which set the masks to be used. The logging function is updated in the log.h so that it honours the mask that the user has set. There's the stub of an ability to change the log destination, but I've just created qemu.log right now.

My changes are all heavily based on https://gitlab.com/qemu-project/qemu/-/blob/master/util/log.c although I did a couple of things a little differently.

It's not a PR - it's nowhere near production ready - but it's good enough to get a little more debug.

In any case, with that then compiled into the Unicorn package, and with the test code updated to change that end section to:

# <<<<< THIS IS WHERE BEHAVIOUR BEGINS TO DIFFER >>>>>
try:
    uc2.ctl_set_debug_mask(0xFFFFFFFF)
except Exception as exc:
    print("Could not turn on debug mask: %s" % (exc,))
dump_registers(uc2)
uc2.hook_add(unicorn.UC_HOOK_CODE, hook_code)
uc2.emu_start(value_40903, 0, timeout=0)  # None
dump_registers(uc2)

This sets the mask to 'everything' and then continues the execution. My code produces the following output:

arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0
arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0
msr_mask: flags 00000001
          valid mask = ff1fffff (features=1fa71ef979)
          In user mode (no priviledge changes)
         = mask 00000000
trans_MSR_reg: mask 00000000
gen_set_psr: mask 00000000
gen_set_cpsr: mask 00000000
cpsr_write: 60000010, mask 00000000
arm_tr_init_disas_context: dc->user = 0, dc->mmu_idx = 26, dc->current_el = 3
msr_mask: flags 00000008
          valid mask = ff1fffff (features=1fa71ef979)
         = mask f8000000
trans_MSR_reg: mask f8000000
gen_set_psr: mask f8000000
gen_set_cpsr: mask f8000000
cpsr_write: effeed05, mask f8000000
arm_tr_init_disas_context: dc->user = 0, dc->mmu_idx = 26, dc->current_el = 3
arm_tr_init_disas_context: dc->user = 0, dc->mmu_idx = 26, dc->current_el = 3

This is for the execution of the code block which is disassembled in the original code ie:

0x07017918: movvc    r1, r0
0x0701791c: bvc      #0x7017934
0x07017934: ldm      sp!, {lr}
0x07017938: msr      cpsr_c, r6
0x0701793c: pop      {lr}
0x07017940: msr      apsr_nzcvq, lr
0x07017944: pop      {r0, r2, r3, r4, r5, r6, pc}

There are two MSR operations listed, the first of which is the msr cpsr_c, r6 and this tells us that it's In user mode (no priviledge changes) - if we look at the earlier debug output it thinks dc->user = 1, which it shouldn't be as CPSR contains 0x13 as the mode.

That's as far as I think I can go - I don't know how it ended up thinking it was in USR mode again, when it's not. Is that any help?

@wtdcode
Copy link
Member

wtdcode commented Jan 11, 2022

The UNICORN_DEBUG looks like it's related to tcg operations? I wasn't sure that was useful.

I have made some changes to my debug branch which has extra debug to help with isolating the mode problems that I encountered. The debug branch is here: https://github.com/gerph/unicorn/tree/cjf-debug-privileged-mode-problems and the change that adds the new runtime controls is here: gerph@38fb9b5 (not sure why the PPC things changed- I haven't touched that area of the code).

Basically, I introduced a new ctl_set_debug_mask() function in the Python side, which passes to a UC_CTL_DEBUG_MASK control in the C side, which calls to qemu_set_loglevel or qemu_get_loglevel which set the masks to be used. The logging function is updated in the log.h so that it honours the mask that the user has set. There's the stub of an ability to change the log destination, but I've just created qemu.log right now.

My changes are all heavily based on https://gitlab.com/qemu-project/qemu/-/blob/master/util/log.c although I did a couple of things a little differently.

It's not a PR - it's nowhere near production ready - but it's good enough to get a little more debug.

In any case, with that then compiled into the Unicorn package, and with the test code updated to change that end section to:

# <<<<< THIS IS WHERE BEHAVIOUR BEGINS TO DIFFER >>>>>
try:
    uc2.ctl_set_debug_mask(0xFFFFFFFF)
except Exception as exc:
    print("Could not turn on debug mask: %s" % (exc,))
dump_registers(uc2)
uc2.hook_add(unicorn.UC_HOOK_CODE, hook_code)
uc2.emu_start(value_40903, 0, timeout=0)  # None
dump_registers(uc2)

This sets the mask to 'everything' and then continues the execution. My code produces the following output:

arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0
arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0
msr_mask: flags 00000001
          valid mask = ff1fffff (features=1fa71ef979)
          In user mode (no priviledge changes)
         = mask 00000000
trans_MSR_reg: mask 00000000
gen_set_psr: mask 00000000
gen_set_cpsr: mask 00000000
cpsr_write: 60000010, mask 00000000
arm_tr_init_disas_context: dc->user = 0, dc->mmu_idx = 26, dc->current_el = 3
msr_mask: flags 00000008
          valid mask = ff1fffff (features=1fa71ef979)
         = mask f8000000
trans_MSR_reg: mask f8000000
gen_set_psr: mask f8000000
gen_set_cpsr: mask f8000000
cpsr_write: effeed05, mask f8000000
arm_tr_init_disas_context: dc->user = 0, dc->mmu_idx = 26, dc->current_el = 3
arm_tr_init_disas_context: dc->user = 0, dc->mmu_idx = 26, dc->current_el = 3

This is for the execution of the code block which is disassembled in the original code ie:

0x07017918: movvc    r1, r0
0x0701791c: bvc      #0x7017934
0x07017934: ldm      sp!, {lr}
0x07017938: msr      cpsr_c, r6
0x0701793c: pop      {lr}
0x07017940: msr      apsr_nzcvq, lr
0x07017944: pop      {r0, r2, r3, r4, r5, r6, pc}

There are two MSR operations listed, the first of which is the msr cpsr_c, r6 and this tells us that it's In user mode (no priviledge changes) - if we look at the earlier debug output it thinks dc->user = 1, which it shouldn't be as CPSR contains 0x13 as the mode.

That's as far as I think I can go - I don't know how it ended up thinking it was in USR mode again, when it's not. Is that any help?

That seems the key! Could you confirm how dc->current_el changes?

@gerph
Copy link
Contributor Author

gerph commented Jan 11, 2022

Ok, I've added the debug code earlier in the test run so that I can see what's going on further back, and I can see that when I do a SWI 0x20016, which is meant to switch execution to SVC mode, that doesn't seem to take effect.

New test code:

ucbroken2.py.gz

This has the debug happening much higher up, and I've interleaved the debug output with the QEmu output by symlinking qemu.log to /dev/tty:

Testing under Unicorn : (2, 0, 33554437L)
Header version: (2, 0, 5)
Registers: 
   r0 : 0x07014e2c   r1 : 0x0000a4dc   r2 : 0x00000003   r3 : 0x04109a24
   r4 : 0x00000000   r5 : 0x00000019   r6 : 0xffffffff   r7 : 0x00000000
   r8 : 0x00000000   r9 : 0x0000a21c  r10 : 0x0000b2b0  r11 : 0x0000c04c
  r12 : 0xf8ff2660   sp : 0x0000c004   lr : 0x60000010   pc : 0x07014f20
  CPSR = 0x20000010
  SPSR = 0x00000000
arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0
0x07014f20: movvs    r3, #0x100
0x07014f24: bvs      #0x7014f48
arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0
0x07014f28: mov      r3, #0
0x07014f2c: ldrb     r0, [r1], #1
0x07014f30: sub      r0, r0, #0x30
0x07014f34: cmp      r0, #0xa
0x07014f38: bhs      #0x7014f48
arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0
0x07014f3c: add      r3, r3, r3, lsl #2
0x07014f40: add      r3, r0, r3, lsl #1
0x07014f44: b        #0x7014f2c
arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0
0x07014f2c: ldrb     r0, [r1], #1
0x07014f30: sub      r0, r0, #0x30
0x07014f34: cmp      r0, #0xa
0x07014f38: bhs      #0x7014f48
0x07014f3c: add      r3, r3, r3, lsl #2
0x07014f40: add      r3, r0, r3, lsl #1
0x07014f44: b        #0x7014f2c
0x07014f2c: ldrb     r0, [r1], #1
0x07014f30: sub      r0, r0, #0x30
0x07014f34: cmp      r0, #0xa
0x07014f38: bhs      #0x7014f48
0x07014f3c: add      r3, r3, r3, lsl #2
0x07014f40: add      r3, r0, r3, lsl #1
0x07014f44: b        #0x7014f2c
0x07014f2c: ldrb     r0, [r1], #1
0x07014f30: sub      r0, r0, #0x30
0x07014f34: cmp      r0, #0xa
0x07014f38: bhs      #0x7014f48
arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0
0x07014f48: add      r0, pc, #0x1dc
0x07014f4c: bl       #0x7017808
arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0
0x07017808: push     {r0, r1, r2, r3, r4, r5, r6, r7, r8, r12, lr}
0x0701780c: mrs      lr, apsr
0x07017810: str      lr, [sp, #-4]!
0x07017814: add      r0, r0, #4
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0
0x07017824: add      r0, r0, #3
0x07017828: bic      r0, r0, #3
0x0701782c: ldr      r8, [r0]
0x07017830: bl       #0x70178cc
arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0
0x070178cc: push     {r0, r2, r3, r4, r5, r6, lr}
0x070178d0: mrs      lr, apsr
0x070178d4: str      lr, [sp, #-4]!
0x070178d8: mrs      r6, apsr
0x070178dc: svc      #0x20016
SWI OS_EnterOS executed
Registers: 
   r0 : 0x07015148   r1 : 0x0000a4e0   r2 : 0x00000003   r3 : 0x00000100
   r4 : 0x00000000   r5 : 0x00000019   r6 : 0x60000010   r7 : 0x00000000
   r8 : 0x00800e06   r9 : 0x0000a21c  r10 : 0x0000b2b0  r11 : 0x0000c04c
  r12 : 0xf8ff2660   sp : 0x0000bfb4   lr : 0x60000010   pc : 0x070178e0
  CPSR = 0x60000010
  SPSR = 0x00000000
cpsr_write: 60000013, mask ffffffff
cpsr_write: 60000013, mask ffffffff
cpsr_write: 60000013, mask ffffffff
cpsr_write: 60000013, mask ffffffff
cpsr_write: 60000010, mask ffffffff
AArch32 mode switch from svc to usr PC 0x70178e0
cpsr_write: 60000013, mask ffffffff
Should now be in SVC32
Registers: 
   r0 : 0x07015148   r1 : 0x0000a4e0   r2 : 0x00000003   r3 : 0x00000100
   r4 : 0x00000000   r5 : 0x00000019   r6 : 0x60000010   r7 : 0x00000000
   r8 : 0x00800e06   r9 : 0x0000a21c  r10 : 0x0000b2b0  r11 : 0x0000c04c
  r12 : 0xf8ff2660   sp : 0x04107fe0   lr : 0x00000000   pc : 0x070178e0
  CPSR = 0x60000013
  SPSR = 0x00000013
arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0
0x070178e0: stmdb    sp!, {lr}
0x070178e4: mov      r5, #0
0x070178e8: ldr      r1, [r5, #0xff0]
0x070178ec: cmp      r1, #0
0x070178f0: bne      #0x7017934
arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0
0x070178f4: mov      r0, #6
0x070178f8: mov      r3, #0x10
0x070178fc: svc      #0x2001e
cpsr_write: 60000013, mask ffffffff
cpsr_write: 60000013, mask ffffffff
cpsr_write: 60000013, mask ffffffff
cpsr_write: 60000013, mask ffffffff
arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0
0x07017900: bvs      #0x7017934
arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0
0x07017904: str      r2, [r5, #0xff0]
0x07017908: mov      r0, r2
0x0701790c: sub      r1, pc, #0x70
0x07017910: mov      r2, #0
0x07017914: svc      #0x61501
cpsr_write: 60000013, mask ffffffff
cpsr_write: 60000013, mask ffffffff
cpsr_write: 60000013, mask ffffffff
cpsr_write: 60000013, mask ffffffff
Registers: 
   r0 : 0x070005ac   r1 : 0x070178a4   r2 : 0x00000000   r3 : 0x00000010
   r4 : 0x00000000   r5 : 0x00000000   r6 : 0x60000010   r7 : 0x00000000
   r8 : 0x00800e06   r9 : 0x0000a21c  r10 : 0x0000b2b0  r11 : 0x0000c04c
  r12 : 0xf8ff2660   sp : 0x04107fdc   lr : 0x00000000   pc : 0x07017918
  CPSR = 0x60000013
  SPSR = 0x00000013
arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0
0x07017918: movvc    r1, r0
0x0701791c: bvc      #0x7017934
arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0
msr_mask: flags 00000001
          valid mask = ff1fffff (features=1fa71ef979)
          In user mode (no priviledge changes)
         = mask 00000000
trans_MSR_reg: mask 00000000
gen_set_psr: mask 00000000
gen_set_cpsr: mask 00000000
0x07017934: ldm      sp!, {lr}
0x07017938: msr      cpsr_c, r6
cpsr_write: 60000010, mask 00000000
arm_tr_init_disas_context: dc->user = 0, dc->mmu_idx = 26, dc->current_el = 3
msr_mask: flags 00000008
          valid mask = ff1fffff (features=1fa71ef979)
         = mask f8000000
trans_MSR_reg: mask f8000000
gen_set_psr: mask f8000000
gen_set_cpsr: mask f8000000
0x0701793c: pop      {lr}
0x07017940: msr      apsr_nzcvq, lr
cpsr_write: effeed05, mask f8000000
arm_tr_init_disas_context: dc->user = 0, dc->mmu_idx = 26, dc->current_el = 3
0x07017944: pop      {r0, r2, r3, r4, r5, r6, pc}
arm_tr_init_disas_context: dc->user = 0, dc->mmu_idx = 26, dc->current_el = 3
Registers: 
   r0 : 0x2300000d   r1 : 0x070005ac   r2 : 0x00000000   r3 : 0x00000000
   r4 : 0x00000000   r5 : 0x00000000   r6 : 0x00000013   r7 : 0x00000000
   r8 : 0x00800e06   r9 : 0x0000a21c  r10 : 0x0000b2b0  r11 : 0x0000c04c
  r12 : 0xf8ff2660   sp : 0x04108000   lr : 0xeffeed05   pc : 0x00000000
  CPSR = 0xe8000013
  SPSR = 0x00000013

I've marked the bit where we issue the SWI to enter SVC mode (ie on return from the SWI call we should be in SVC32) with SWI OS_EnterOS executed. That section looks like this:

arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0
0x070178cc: push     {r0, r2, r3, r4, r5, r6, lr}
0x070178d0: mrs      lr, apsr
0x070178d4: str      lr, [sp, #-4]!
0x070178d8: mrs      r6, apsr
0x070178dc: svc      #0x20016
SWI OS_EnterOS executed
Registers: 
   r0 : 0x07015148   r1 : 0x0000a4e0   r2 : 0x00000003   r3 : 0x00000100
   r4 : 0x00000000   r5 : 0x00000019   r6 : 0x60000010   r7 : 0x00000000
   r8 : 0x00800e06   r9 : 0x0000a21c  r10 : 0x0000b2b0  r11 : 0x0000c04c
  r12 : 0xf8ff2660   sp : 0x0000bfb4   lr : 0x60000010   pc : 0x070178e0
  CPSR = 0x60000010
  SPSR = 0x00000000
cpsr_write: 60000013, mask ffffffff
cpsr_write: 60000013, mask ffffffff
cpsr_write: 60000013, mask ffffffff
cpsr_write: 60000013, mask ffffffff
cpsr_write: 60000010, mask ffffffff
AArch32 mode switch from svc to usr PC 0x70178e0
cpsr_write: 60000013, mask ffffffff
Should now be in SVC32
Registers: 
   r0 : 0x07015148   r1 : 0x0000a4e0   r2 : 0x00000003   r3 : 0x00000100
   r4 : 0x00000000   r5 : 0x00000019   r6 : 0x60000010   r7 : 0x00000000
   r8 : 0x00800e06   r9 : 0x0000a21c  r10 : 0x0000b2b0  r11 : 0x0000c04c
  r12 : 0xf8ff2660   sp : 0x04107fe0   lr : 0x00000000   pc : 0x070178e0
  CPSR = 0x60000013
  SPSR = 0x00000013
arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0
0x070178e0: stmdb    sp!, {lr}
0x070178e4: mov      r5, #0
0x070178e8: ldr      r1, [r5, #0xff0]
0x070178ec: cmp      r1, #0
0x070178f0: bne      #0x7017934

On return from that change CPSR is left as 0x60000013, BUT dc->user = 1 and dc->current_el = 0, so the change to SVC mode only happened in the registers, and not in the internal state. That explains why it didn't let us switch back in the later MSR - it thought we were already in USR mode so weren't allowed to.

Is that any more help?

@wtdcode
Copy link
Member

wtdcode commented Jan 11, 2022

Ok, I've added the debug code earlier in the test run so that I can see what's going on further back, and I can see that when I do a SWI 0x20016, which is meant to switch execution to SVC mode, that doesn't seem to take effect.

New test code:

ucbroken2.py.gz

This has the debug happening much higher up, and I've interleaved the debug output with the QEmu output by symlinking qemu.log to /dev/tty:

Testing under Unicorn : (2, 0, 33554437L)
Header version: (2, 0, 5)
Registers: 
   r0 : 0x07014e2c   r1 : 0x0000a4dc   r2 : 0x00000003   r3 : 0x04109a24
   r4 : 0x00000000   r5 : 0x00000019   r6 : 0xffffffff   r7 : 0x00000000
   r8 : 0x00000000   r9 : 0x0000a21c  r10 : 0x0000b2b0  r11 : 0x0000c04c
  r12 : 0xf8ff2660   sp : 0x0000c004   lr : 0x60000010   pc : 0x07014f20
  CPSR = 0x20000010
  SPSR = 0x00000000
arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0
0x07014f20: movvs    r3, #0x100
0x07014f24: bvs      #0x7014f48
arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0
0x07014f28: mov      r3, #0
0x07014f2c: ldrb     r0, [r1], #1
0x07014f30: sub      r0, r0, #0x30
0x07014f34: cmp      r0, #0xa
0x07014f38: bhs      #0x7014f48
arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0
0x07014f3c: add      r3, r3, r3, lsl #2
0x07014f40: add      r3, r0, r3, lsl #1
0x07014f44: b        #0x7014f2c
arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0
0x07014f2c: ldrb     r0, [r1], #1
0x07014f30: sub      r0, r0, #0x30
0x07014f34: cmp      r0, #0xa
0x07014f38: bhs      #0x7014f48
0x07014f3c: add      r3, r3, r3, lsl #2
0x07014f40: add      r3, r0, r3, lsl #1
0x07014f44: b        #0x7014f2c
0x07014f2c: ldrb     r0, [r1], #1
0x07014f30: sub      r0, r0, #0x30
0x07014f34: cmp      r0, #0xa
0x07014f38: bhs      #0x7014f48
0x07014f3c: add      r3, r3, r3, lsl #2
0x07014f40: add      r3, r0, r3, lsl #1
0x07014f44: b        #0x7014f2c
0x07014f2c: ldrb     r0, [r1], #1
0x07014f30: sub      r0, r0, #0x30
0x07014f34: cmp      r0, #0xa
0x07014f38: bhs      #0x7014f48
arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0
0x07014f48: add      r0, pc, #0x1dc
0x07014f4c: bl       #0x7017808
arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0
0x07017808: push     {r0, r1, r2, r3, r4, r5, r6, r7, r8, r12, lr}
0x0701780c: mrs      lr, apsr
0x07017810: str      lr, [sp, #-4]!
0x07017814: add      r0, r0, #4
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
0x07017818: ldrb     lr, [r0], #1
0x0701781c: teq      lr, #0
0x07017820: bne      #0x7017818
arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0
0x07017824: add      r0, r0, #3
0x07017828: bic      r0, r0, #3
0x0701782c: ldr      r8, [r0]
0x07017830: bl       #0x70178cc
arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0
0x070178cc: push     {r0, r2, r3, r4, r5, r6, lr}
0x070178d0: mrs      lr, apsr
0x070178d4: str      lr, [sp, #-4]!
0x070178d8: mrs      r6, apsr
0x070178dc: svc      #0x20016
SWI OS_EnterOS executed
Registers: 
   r0 : 0x07015148   r1 : 0x0000a4e0   r2 : 0x00000003   r3 : 0x00000100
   r4 : 0x00000000   r5 : 0x00000019   r6 : 0x60000010   r7 : 0x00000000
   r8 : 0x00800e06   r9 : 0x0000a21c  r10 : 0x0000b2b0  r11 : 0x0000c04c
  r12 : 0xf8ff2660   sp : 0x0000bfb4   lr : 0x60000010   pc : 0x070178e0
  CPSR = 0x60000010
  SPSR = 0x00000000
cpsr_write: 60000013, mask ffffffff
cpsr_write: 60000013, mask ffffffff
cpsr_write: 60000013, mask ffffffff
cpsr_write: 60000013, mask ffffffff
cpsr_write: 60000010, mask ffffffff
AArch32 mode switch from svc to usr PC 0x70178e0
cpsr_write: 60000013, mask ffffffff
Should now be in SVC32
Registers: 
   r0 : 0x07015148   r1 : 0x0000a4e0   r2 : 0x00000003   r3 : 0x00000100
   r4 : 0x00000000   r5 : 0x00000019   r6 : 0x60000010   r7 : 0x00000000
   r8 : 0x00800e06   r9 : 0x0000a21c  r10 : 0x0000b2b0  r11 : 0x0000c04c
  r12 : 0xf8ff2660   sp : 0x04107fe0   lr : 0x00000000   pc : 0x070178e0
  CPSR = 0x60000013
  SPSR = 0x00000013
arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0
0x070178e0: stmdb    sp!, {lr}
0x070178e4: mov      r5, #0
0x070178e8: ldr      r1, [r5, #0xff0]
0x070178ec: cmp      r1, #0
0x070178f0: bne      #0x7017934
arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0
0x070178f4: mov      r0, #6
0x070178f8: mov      r3, #0x10
0x070178fc: svc      #0x2001e
cpsr_write: 60000013, mask ffffffff
cpsr_write: 60000013, mask ffffffff
cpsr_write: 60000013, mask ffffffff
cpsr_write: 60000013, mask ffffffff
arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0
0x07017900: bvs      #0x7017934
arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0
0x07017904: str      r2, [r5, #0xff0]
0x07017908: mov      r0, r2
0x0701790c: sub      r1, pc, #0x70
0x07017910: mov      r2, #0
0x07017914: svc      #0x61501
cpsr_write: 60000013, mask ffffffff
cpsr_write: 60000013, mask ffffffff
cpsr_write: 60000013, mask ffffffff
cpsr_write: 60000013, mask ffffffff
Registers: 
   r0 : 0x070005ac   r1 : 0x070178a4   r2 : 0x00000000   r3 : 0x00000010
   r4 : 0x00000000   r5 : 0x00000000   r6 : 0x60000010   r7 : 0x00000000
   r8 : 0x00800e06   r9 : 0x0000a21c  r10 : 0x0000b2b0  r11 : 0x0000c04c
  r12 : 0xf8ff2660   sp : 0x04107fdc   lr : 0x00000000   pc : 0x07017918
  CPSR = 0x60000013
  SPSR = 0x00000013
arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0
0x07017918: movvc    r1, r0
0x0701791c: bvc      #0x7017934
arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0
msr_mask: flags 00000001
          valid mask = ff1fffff (features=1fa71ef979)
          In user mode (no priviledge changes)
         = mask 00000000
trans_MSR_reg: mask 00000000
gen_set_psr: mask 00000000
gen_set_cpsr: mask 00000000
0x07017934: ldm      sp!, {lr}
0x07017938: msr      cpsr_c, r6
cpsr_write: 60000010, mask 00000000
arm_tr_init_disas_context: dc->user = 0, dc->mmu_idx = 26, dc->current_el = 3
msr_mask: flags 00000008
          valid mask = ff1fffff (features=1fa71ef979)
         = mask f8000000
trans_MSR_reg: mask f8000000
gen_set_psr: mask f8000000
gen_set_cpsr: mask f8000000
0x0701793c: pop      {lr}
0x07017940: msr      apsr_nzcvq, lr
cpsr_write: effeed05, mask f8000000
arm_tr_init_disas_context: dc->user = 0, dc->mmu_idx = 26, dc->current_el = 3
0x07017944: pop      {r0, r2, r3, r4, r5, r6, pc}
arm_tr_init_disas_context: dc->user = 0, dc->mmu_idx = 26, dc->current_el = 3
Registers: 
   r0 : 0x2300000d   r1 : 0x070005ac   r2 : 0x00000000   r3 : 0x00000000
   r4 : 0x00000000   r5 : 0x00000000   r6 : 0x00000013   r7 : 0x00000000
   r8 : 0x00800e06   r9 : 0x0000a21c  r10 : 0x0000b2b0  r11 : 0x0000c04c
  r12 : 0xf8ff2660   sp : 0x04108000   lr : 0xeffeed05   pc : 0x00000000
  CPSR = 0xe8000013
  SPSR = 0x00000013

I've marked the bit where we issue the SWI to enter SVC mode (ie on return from the SWI call we should be in SVC32) with SWI OS_EnterOS executed. That section looks like this:

arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0
0x070178cc: push     {r0, r2, r3, r4, r5, r6, lr}
0x070178d0: mrs      lr, apsr
0x070178d4: str      lr, [sp, #-4]!
0x070178d8: mrs      r6, apsr
0x070178dc: svc      #0x20016
SWI OS_EnterOS executed
Registers: 
   r0 : 0x07015148   r1 : 0x0000a4e0   r2 : 0x00000003   r3 : 0x00000100
   r4 : 0x00000000   r5 : 0x00000019   r6 : 0x60000010   r7 : 0x00000000
   r8 : 0x00800e06   r9 : 0x0000a21c  r10 : 0x0000b2b0  r11 : 0x0000c04c
  r12 : 0xf8ff2660   sp : 0x0000bfb4   lr : 0x60000010   pc : 0x070178e0
  CPSR = 0x60000010
  SPSR = 0x00000000
cpsr_write: 60000013, mask ffffffff
cpsr_write: 60000013, mask ffffffff
cpsr_write: 60000013, mask ffffffff
cpsr_write: 60000013, mask ffffffff
cpsr_write: 60000010, mask ffffffff
AArch32 mode switch from svc to usr PC 0x70178e0
cpsr_write: 60000013, mask ffffffff
Should now be in SVC32
Registers: 
   r0 : 0x07015148   r1 : 0x0000a4e0   r2 : 0x00000003   r3 : 0x00000100
   r4 : 0x00000000   r5 : 0x00000019   r6 : 0x60000010   r7 : 0x00000000
   r8 : 0x00800e06   r9 : 0x0000a21c  r10 : 0x0000b2b0  r11 : 0x0000c04c
  r12 : 0xf8ff2660   sp : 0x04107fe0   lr : 0x00000000   pc : 0x070178e0
  CPSR = 0x60000013
  SPSR = 0x00000013
arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0
0x070178e0: stmdb    sp!, {lr}
0x070178e4: mov      r5, #0
0x070178e8: ldr      r1, [r5, #0xff0]
0x070178ec: cmp      r1, #0
0x070178f0: bne      #0x7017934

On return from that change CPSR is left as 0x60000013, BUT dc->user = 1 and dc->current_el = 0, so the change to SVC mode only happened in the registers, and not in the internal state. That explains why it didn't let us switch back in the later MSR - it thought we were already in USR mode so weren't allowed to.

Is that any more help?

AFAIK, you have to handle everything for an SWI instruction within an interrupt callback, right? How do you handle that? It's really hard to read your script and I'm still not able to understand what's wrong here.

@gerph
Copy link
Contributor Author

gerph commented Jan 11, 2022

In my OS I don't handle it in the interrupt callback but stop the emulation then emu_start returns, and then we do the real work. I'll go through the code that's executed by after the service call returns to explain what's going on (at least, as well as I can, because there's a lot of stuff in the main OS that is perfectly sane in the original OS, but seems very weird here.

I'll work through the code in the OS and try to explain each operation that the OS peforms and how that equates to what's going on in the script.

Here's the script code in the area of the problem:

uc2.emu_start(value_40542, 0, timeout=0)  # (accessed but not returned yet)
#### Returning from this we're trying to execute a SWI 0x20016 : OS_EnterOS (enters SVC mode)
print("SWI OS_EnterOS executed")
dump_registers(uc2)
#  uc2.hook_add:1:hook_intr(uc2, 2L, None)  # (accessed but not returned yet)
#    uc2.emu_stop()  # None
  # None  (return from uc2.hook_add:1:hook_intr(uc2, 2L, None))
# None  (return from uc2.emu_start(value_40542, 0, timeout=0))
value_40543 = uc2.mem_read(0, 32)  # bytearray(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
value_40544 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_PC)  # 0x70178e0
value_40545 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_PC)  # 0x70178e0
value_40546 = uc2.mem_read(0x70178dc, 4)  # bytearray(b'\x16\x00\x02\xef')
value_40547 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_PC)  # 0x70178e0
value_40548 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_CPSR)  # 0x60000010
value_40549 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_CPSR)  # 0x60000010
value_40550 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_CPSR)  # 0x60000010
uc2.reg_write(unicorn.arm_const.UC_ARM_REG_CPSR, 0x60000013)  # None
value_40551 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_CPSR)  # 0x60000013
uc2.reg_write(unicorn.arm_const.UC_ARM_REG_CPSR, value_40551)  # None
value_40552 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_CPSR)  # 0x60000013
uc2.reg_write(unicorn.arm_const.UC_ARM_REG_CPSR, value_40552)  # None
value_40553 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_CPSR)  # 0x60000013
uc2.reg_write(unicorn.arm_const.UC_ARM_REG_CPSR, value_40553)  # None
value_40554 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_CPSR)  # 0x60000013
uc2.reg_write(unicorn.arm_const.UC_ARM_REG_CPSR, value_40550)  # None
value_40555 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_CPSR)  # 0x60000010
uc2.reg_write(unicorn.arm_const.UC_ARM_REG_CPSR, value_40554)  # None
value_40556 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_PC)  # 0x70178e0
value_40557 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_CPSR)  # 0x60000013
value_40558 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_CPSR)  # 0x60000013
value_40559 = uc2.mem_read(0, 32)  # bytearray(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
value_40560 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_PC)  # 0x70178e0
print("Should now be in SVC32")
dump_registers(uc2)
uc2.emu_start(value_40560, 0, timeout=0)  # (accessed but not returned yet)

And hopefully I can annotate what's going on...

uc2.emu_start(value_40542, 0, timeout=0)  # (accessed but not returned yet)
#### Returning from this we're trying to execute a SWI 0x20016 : OS_EnterOS (enters SVC mode)
print("SWI OS_EnterOS executed")
dump_registers(uc2)
#  uc2.hook_add:1:hook_intr(uc2, 2L, None)  # (accessed but not returned yet)
#    uc2.emu_stop()  # None
  # None  (return from uc2.hook_add:1:hook_intr(uc2, 2L, None))
# None  (return from uc2.emu_start(value_40542, 0, timeout=0))

This section tries to execute some code the code, and requests that the execution stop at address 0. The code that is executed ends with:

arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0
0x070178cc: push     {r0, r2, r3, r4, r5, r6, lr}
0x070178d0: mrs      lr, apsr
0x070178d4: str      lr, [sp, #-4]!
0x070178d8: mrs      r6, apsr
0x070178dc: svc      #0x20016

And then Unicorn calls hook_intr which I've registered as an exception handler. The debug prints this as a call with the parameters (uc2, 2L, None) - 2 is the value of the exception vector that's being called, which the the service call (SWI) vector. The actual implementation of hook_intr is:

    def hook_intr(self, uc, intno, data):
        """
        Interrupt or system call (the system entry points)
        """
        self.exit_reason = ('exception', intno)
        self.emu.emu_stop()

which is why the script says that we call emu_stop in the comment.

This causes the emu_start() to return. Because the emulation has ended, watchpoints are updated so that we can check whether the SWI call will change things. The OS has watchpoints on addresses 0 - 32, so we read what's in there:

value_40543 = uc2.mem_read(0, 32)  # bytearray(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')

Next we check whether the reason that we stopped execution was because we executed code at address 0 (because 0 is the address that we've told it to stop at in the emu_start). That generates a special internal error in the OS. The original python code looks like this:

                    if self.regs.pc == 0x0:
                        self.exit_reason = ('branchthrough0',)

and this translates into the first read of pc:

value_40544 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_PC)  # 0x70178e0

Next we check the reason for exiting execution (which we set in hook_intr), so that we can read the SWI number that was invoked:

                    elif exit_reason[0] == 'exception':
                        ...
                        if intno == 2:
                            # SWI call.
                            pc = self.regs[15] - 4
                            if self.debug_swi:
                                print("SWI at &%08x" % (pc,))
                            value = self.memory.read_word(pc)

This equates to the next two Unicorn operations:

value_40545 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_PC)  # 0x70178e0
value_40546 = uc2.mem_read(0x70178dc, 4)  # bytearray(b'\x16\x00\x02\xef')

Having determined the SWI number we dispatch this to the Kernel...

                                        self.kernel.swi_dispatch(swi)

The SWI dispatch code looks like this:

    def swi_dispatch(self, swi):
        """
        SWI call by the user.
        """
        try:
            self.swi_enter(swi, self.ro.regs.pc)
            self._swi_dispatch(swi)
            self.swi_leave(swi, self.ro.regs.pc)

        except RISCOSError as exc:
            self.swi_leave(swi, self.ro.regs.pc, error=exc)
            raise

So the call to swi_enter will read the pc, which equates to:

value_40547 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_PC)  # 0x70178e0

Then we head into _swi_dispatch which does nothing relevant, but calls into internal_swi_dispatch which checks whether you called a SWI call in IRQ mode (which would be bad):

        if self.ro.regs.cpsr_mode == self.ro.regs.MODE_IRQ:
            self.ro.trace.warning(name='SWI misuse',
                                  label="SWI called from within IRQ mode: &{:x} ({})".format(swi,
                                                                                             self.ro.kernel.swi_name(swi)))

That gives us one of the CPSR reads:

value_40548 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_CPSR)  # 0x60000010

And as we can see we're in USR32 mode (0x10).

Then we enter a context handler which ensures that we're in SVC mode:

        with SVCMode(self.ro) as mode:

The entry sequence of this code is:

    def __enter__(self):
        self.old_mode = self.ro.regs.cpsr
        self.ro.regs.cpsr_mode = self.want_mode
        return self

where want_mode is SVC32 (0x13).
The read of the CPSR is the first of the next two operations, and the setter function for cpsr_mode does this:

   @cpsr_mode.setter
    def cpsr_mode(self, value):
        value = int(value) & self.MODE_MASK
        current_cpsr = self.cpsr
        self.emu.reg_write(arm.UC_ARM_REG_CPSR, (current_cpsr & ~self.MODE_MASK) | value)

This between the SVCMode __enter__ function and this cpsr_mode assignment this explains the operations:

value_40549 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_CPSR)  # 0x60000010
value_40550 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_CPSR)  # 0x60000010
uc2.reg_write(unicorn.arm_const.UC_ARM_REG_CPSR, 0x60000013)  # None

So now we're in SVC32, and there's an odd sequence of 'read CPSR, set CPSR to the same value'. The first pair of these because we clear the V flag:

            if swin not in (swis.OS_CallAVector, swis.OS_BreakPt):
                self.ro.regs.cpsr_v = False

The manipulation of the cpsr_v in its setter is essentially read CPSR, clear V, write CPSR. So this explains two more lines:

value_40551 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_CPSR)  # 0x60000013
uc2.reg_write(unicorn.arm_const.UC_ARM_REG_CPSR, value_40551)  # None

The kernel calls a dispatcher to find the SWI calls that we have registered - this is implemented as:

@handlers.swi.register(swis.OS_EnterOS)
def swi_OS_EnterOS(ro, swin, regs):
    """
    OS_EnterOS

    <= In SVC32 mode
    """

    if ro.config['apiwarnings.enterleaveos']:
        ro.trace.warning(name='API warning',
                         label="OS_EnterOS SWI should not be used in normal applications")

    regs.cpsr_mode = regs.MODE_SVC

So this cpsr_mode assignment explains the next two read-write sequences:

value_40552 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_CPSR)  # 0x60000013
uc2.reg_write(unicorn.arm_const.UC_ARM_REG_CPSR, value_40552)  # None

We then return to the Kernel having successfully dispatched the SWI to the right place, and because no error was raised we clear the V flag (RISC OS uses the V flag on return from SWI calls to indicate errors):

                # Successful execution clears V
                self.ro.regs.cpsr_v = False

So this explains the next two CPSR read-write sequences:

value_40553 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_CPSR)  # 0x60000013
uc2.reg_write(unicorn.arm_const.UC_ARM_REG_CPSR, value_40553)  # None

Then we leave the SVCMode context handler and run the __exit__ code:

    def __exit__(self, exctype, value, tb):
        if value and not isinstance(value, RISCOSError):
            # This is an exception that isn't a RISC OS Error, so we don't touch the mode
            pass
        else:
            # Restore the mode
            self.ro.regs.cpsr_mode = self.old_mode

There was no exception, so just the CPSR mode changes back to the mode it was when we entered the SWI, which was USR32. This is the next two CPSR read-write sequences:

value_40554 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_CPSR)  # 0x60000013
uc2.reg_write(unicorn.arm_const.UC_ARM_REG_CPSR, value_40550)  # None

The value_40550 is the mode that we were in when we entered the Kernel swi_dispatch code, which is USR32. This also corresponds to the extra debug that came out when the special debug mask was set:

cpsr_write: 60000010, mask ffffffff
AArch32 mode switch from svc to usr PC 0x70178e0

That's this code being triggered: https://github.com/gerph/unicorn/blob/cjf-debug-privileged-mode-problems/qemu/target/arm/helper.c#L8015

The kernel is about to return from the SWI dispatcher but it has to fix things up, because although the SWI handler for OS_EnterOS switched to SVC32, it was thwarted by the SVCMode context handler restoring the mode to the entry mode:

So this code is run:

        if swin == swis.OS_EnterOS:
            # Put the mode back to SVC (whatever we might have restored)
            self.ro.regs.cpsr_mode = self.ro.regs.MODE_SVC

which is the next two CPSR read-write sequences in the script:

value_40555 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_CPSR)  # 0x60000010
uc2.reg_write(unicorn.arm_const.UC_ARM_REG_CPSR, value_40554)  # None

This leave us in SVC32. In the debug output it's interesting that this only outputs:

cpsr_write: 60000013, mask ffffffff

There is no corresponding message saying AArch32 mode switch to say that we're returning to SVC32 here. That might be a problem, but I'm not sure about that area.

At this point we are now leaving the internal_swi_dispatch code, and we return to swi_dispatch (which was far up there in this description):

            self.swi_leave(swi, self.ro.regs.pc)

This accounts for the reading of the pc:

value_40556 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_PC)  # 0x70178e0

The swi_leave code checks whether the V flag is set so that it can report errors to the tracing system. This accounts for reading the CPSR:

value_40557 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_CPSR)  # 0x60000013

We come back to the main system execution loop and we check whether we are about to return to USR mode (if we did, we would trigger transient callbacks):

                    if self.regs.cpsr_mode == self.regs.MODE_USR:

This explains the script line:

value_40558 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_CPSR)  # 0x60000013

Back at the beginning of the emulation handling we remembered the contents of all the watchpoints. This is so that we can trigger trace event if the watchpoints changed during the SWI call (as hooks for accessing memory don't fire if you're doing it in the python code). So before we go back into the emulation loop we read the watchpoint at 0 - 32 to check if there were any changes:

value_40559 = uc2.mem_read(0, 32)  # bytearray(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')

Finally, we want to execute the next section of code, so we read the PC to pass to emu_start:

value_40560 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_PC)  # 0x70178e0
print("Should now be in SVC32")
dump_registers(uc2)
uc2.emu_start(value_40560, 0, timeout=0)  # (accessed but not returned yet)

At this point our CPSR is set to 0x13, but the next line of debug which is output from the emu_start debug masked output is:

arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0

So the change to SVC32 was not effective.


I accept that that explanation may not have been any more helpful.

But I think in reading through it I may be able to lift that bit of code out and put it in a smaller test program... Back in a mo... fingers crossed...

@gerph
Copy link
Contributor Author

gerph commented Jan 11, 2022

Right I have a repro case in slightly fewer lines:

#!/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>

import sys
import struct

import capstone

import unicorn
from unicorn import (
        uc_version,
        UcError,
        Uc,
        UC_ARCH_ARM, UC_MODE_ARM,
        UC_ERR_EXCEPTION,
        UC_VERSION_MAJOR, UC_VERSION_MINOR, UC_VERSION_EXTRA)
from unicorn.arm_const import (UC_ARM_REG_R0,
        UC_ARM_REG_R1,
        UC_ARM_REG_R2,
        UC_ARM_REG_R3,
        UC_ARM_REG_R4,
        UC_ARM_REG_R5,
        UC_ARM_REG_R6,
        UC_ARM_REG_R7,
        UC_ARM_REG_R8,
        UC_ARM_REG_R9,
        UC_ARM_REG_R10,
        UC_ARM_REG_R11,
        UC_ARM_REG_R12,
        UC_ARM_REG_R13,
        UC_ARM_REG_R14,
        UC_ARM_REG_R15,
        UC_ARM_REG_SP,
        UC_ARM_REG_LR,
        UC_ARM_REG_PC,
        UC_ARM_REG_CPSR,
        UC_ARM_REG_SPSR)

reg_map = [
        UC_ARM_REG_R0,
        UC_ARM_REG_R1,
        UC_ARM_REG_R2,
        UC_ARM_REG_R3,
        UC_ARM_REG_R4,
        UC_ARM_REG_R5,
        UC_ARM_REG_R6,
        UC_ARM_REG_R7,
        UC_ARM_REG_R8,
        UC_ARM_REG_R9,
        UC_ARM_REG_R10,
        UC_ARM_REG_R11,
        UC_ARM_REG_R12,
        UC_ARM_REG_SP,
        UC_ARM_REG_LR,
        UC_ARM_REG_PC,
    ]
arm_names = [
        'r0', 'r1', 'r2', 'r3',
        'r4', 'r5', 'r6', 'r7',
        'r8', 'r9', 'r10', 'r11',
        'r12', 'sp', 'lr', 'pc'
    ]


ADDRESS = 0x1000
ARM_INSTS = """
E10F6000 : MRS     r6, apsr
E3C6601F : BIC     r6, r6, #&1F
E3866010 : ORR     r6, r6, #&10            ; switch to USR32
E121F006 : MSR     cpsr_c, r6
EF020016 : SWI     OS_EnterOS              ; marker for our failing code
E121F006 : MSR     cpsr_c, r6              ; return to USR32
E1A00000 : MOV     r0,r0
EF000011 : SWI     OS_Exit
"""
ARM_WORDS = []
for line in ARM_INSTS.splitlines():
    if ' ' not in line or line.startswith('#'):
        continue
    word = line.split(' ')[0]
    value = int(word, 16)
    ARM_WORDS.append(struct.pack('<L', value))
ARM_CODE = b''.join(ARM_WORDS)

# Keep a copy of the code we're running.
with open('code', 'wb') as fh:
    fh.write(ARM_CODE)

def dump_registers(uc):
    print("Registers: ")
    for rn in range(0, 16):
        value = uc.reg_read(reg_map[rn])
        sys.stdout.write("  %3s : 0x%08x" % (arm_names[rn], value))
        if rn % 4 == 3:
            sys.stdout.write("\n")
    print("  CPSR = 0x{:08x}".format(uc.reg_read(UC_ARM_REG_CPSR)))
    print("  SPSR = 0x{:08x}".format(uc.reg_read(UC_ARM_REG_SPSR)))


def read_word(uc, address):
    data = uc.mem_read(address, 4)
    word = data[0] | (data[1]<<8) | (data[2]<<16) | (data[3]<<24)
    return word


def diss(codeword, address):
    for i in md.disasm(codeword, address):
        print("0x%08x: %-8s %s" %(i.address, i.mnemonic, i.op_str))

def hook_code(uc, address, size, user_data):
    codeword = uc.mem_read(address, 4)
    diss(codeword, address)

md = capstone.Cs(capstone.CS_ARCH_ARM, capstone.CS_MODE_ARM)
md.detail = True
md.syntax = capstone.CS_OPT_SYNTAX_NOREGNAME


# 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("Changing ARM modes")
    try:
        # Initialize emulator in ARM mode
        mu = Uc(UC_ARCH_ARM, UC_MODE_ARM)

        # initialize machine registers in different modes
        mu.reg_write(UC_ARM_REG_CPSR, 0x40000013)   # Current mode = SVC32 mode
        mu.reg_write(UC_ARM_REG_SPSR, 0x40000013)   # Saved mode = SVC32 mode
        mu.reg_write(UC_ARM_REG_R13, 0x12345678)    # SVC stack value
        mu.reg_write(UC_ARM_REG_R14, 0x00102220)    # SVC link value
        print("--- Should be in SVC32, with R13 = 0x12345678, R14 = 0x00102220")
        dump_registers(mu)

        mu.reg_write(UC_ARM_REG_CPSR, 0x40000010)   # Current mode = USR32 mode
        mu.reg_write(UC_ARM_REG_R13, 0x0010000)     # USR stack value
        mu.reg_write(UC_ARM_REG_R14, 0x0001234)     # USR link value
        print("--- Should be in USR32, with R13 = 0x00010000, R14 = 0x0001234")
        dump_registers(mu)

        # Now the actual test, we're in SVC mode and we are going to execute some code
        mu.reg_write(UC_ARM_REG_CPSR, 0x40000013)   # Current mode = SVC32 mode

        mu.mem_map(ADDRESS, 4096)                   # 4K at the execution address
        mu.mem_write(ADDRESS, ARM_CODE)

        uc2 = mu
        uc2.hook_add(unicorn.UC_HOOK_CODE, hook_code)
        try:
            uc2.ctl_set_debug_mask(0xFFFFFFFF)
        except Exception as exc:
            print("Could not turn on debug mask: %s" % (exc,))

        print("--- Executing code at 0x{:08x}".format(ADDRESS))
        mu.reg_write(UC_ARM_REG_R15, ADDRESS)
        while True:
            pc = mu.reg_read(UC_ARM_REG_R15)
            print("Run code at 0x{:08x}".format(pc))
            dump_registers(mu)
            try:
                mu.emu_start(pc, ADDRESS + len(ARM_CODE))
            except UcError as e:
                if e.errno != UC_ERR_EXCEPTION:
                    raise
                # This is an exception call, so we want to process the SWI call.
                pc = mu.reg_read(UC_ARM_REG_R15) - 4
                word = read_word(mu, pc)
                swi_number = word & 0xFFFFFF
                print("--- Reached SWI at 0x{:08x} (0x{:x})".format(pc, swi_number))
                if swi_number == 0x11:
                    # OS_Exit - stop looping
                    break
                if swi_number == 0x20016:
                    # OS_EnterOS
                    print("OS_EnterOS emulation")

                    # This is the section of code from our failing program:
                    # I've commented out the sections of the code that don't seem to matter.

                    # >>>> Failing code
                    #value_40543 = uc2.mem_read(0, 32)  # bytearray(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
                    #value_40544 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_PC)  # 0x70178e0
                    #value_40545 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_PC)  # 0x70178e0
                    #value_40546 = uc2.mem_read(0x70178dc, 4)  # bytearray(b'\x16\x00\x02\xef')
                    #value_40547 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_PC)  # 0x70178e0
                    #value_40548 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_CPSR)  # 0x60000010
                    #value_40549 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_CPSR)  # 0x60000010
                    value_40550 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_CPSR)  # 0x60000010
                    uc2.reg_write(unicorn.arm_const.UC_ARM_REG_CPSR, 0x60000013)  # None
                    #value_40551 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_CPSR)  # 0x60000013
                    #uc2.reg_write(unicorn.arm_const.UC_ARM_REG_CPSR, value_40551)  # None
                    #value_40552 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_CPSR)  # 0x60000013
                    #uc2.reg_write(unicorn.arm_const.UC_ARM_REG_CPSR, value_40552)  # None
                    #value_40553 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_CPSR)  # 0x60000013
                    #uc2.reg_write(unicorn.arm_const.UC_ARM_REG_CPSR, value_40553)  # None
                    value_40554 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_CPSR)  # 0x60000013
                    uc2.reg_write(unicorn.arm_const.UC_ARM_REG_CPSR, value_40550)  # None
                    value_40555 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_CPSR)  # 0x60000010
                    uc2.reg_write(unicorn.arm_const.UC_ARM_REG_CPSR, value_40554)  # None
                    #value_40556 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_PC)  # 0x70178e0
                    #value_40557 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_CPSR)  # 0x60000013
                    #value_40558 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_CPSR)  # 0x60000013
                    #value_40559 = uc2.mem_read(0, 32)  # bytearray(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
                    #value_40560 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_PC)  # 0x70178e0
                    print("Should now be in SVC32")
                    # <<<< End failing code

                else:
                    # Unrecognised SWI call
                    raise

        print("--- Should be in USR32, with R13 = 0x00010000, R14 = 0x0001234")
        dump_registers(mu)

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


if __name__ == '__main__':
    test_arm()

I've lifted the section I explained from the scripted code (I should have done this earlier!), and put it into one of my test harnesses. All the bits that aren't relevant have been commented out which leaves us with a short sequence that seems to go wrong.

In the python code, with the state set to USR32:

  • CPSR = SVC32
  • CPSR = USR32
  • CPSR = SVC32

And now we're in SVC32 according to CPSR, BUT the system thinks dc->current_el = 0, so we cannot change modes with MSR.

Failing output (Unicorn 2)

Testing under Unicorn : (2, 0, 33554437L)
Header version: (2, 0, 5)
Changing ARM modes
--- Should be in SVC32, with R13 = 0x12345678, R14 = 0x00102220
Registers: 
   r0 : 0x00000000   r1 : 0x00000000   r2 : 0x00000000   r3 : 0x00000000
   r4 : 0x00000000   r5 : 0x00000000   r6 : 0x00000000   r7 : 0x00000000
   r8 : 0x00000000   r9 : 0x00000000  r10 : 0x00000000  r11 : 0x00000000
  r12 : 0x00000000   sp : 0x12345678   lr : 0x00102220   pc : 0x00000000
  CPSR = 0x40000013
  SPSR = 0x40000013
--- Should be in USR32, with R13 = 0x00010000, R14 = 0x0001234
Registers: 
   r0 : 0x00000000   r1 : 0x00000000   r2 : 0x00000000   r3 : 0x00000000
   r4 : 0x00000000   r5 : 0x00000000   r6 : 0x00000000   r7 : 0x00000000
   r8 : 0x00000000   r9 : 0x00000000  r10 : 0x00000000  r11 : 0x00000000
  r12 : 0x00000000   sp : 0x00010000   lr : 0x00001234   pc : 0x00000000
  CPSR = 0x40000010
  SPSR = 0x00000000
--- Executing code at 0x00001000
Run code at 0x00001000
Registers: 
   r0 : 0x00000000   r1 : 0x00000000   r2 : 0x00000000   r3 : 0x00000000
   r4 : 0x00000000   r5 : 0x00000000   r6 : 0x00000000   r7 : 0x00000000
   r8 : 0x00000000   r9 : 0x00000000  r10 : 0x00000000  r11 : 0x00000000
  r12 : 0x00000000   sp : 0x12345678   lr : 0x00102220   pc : 0x00001000
  CPSR = 0x40000013
  SPSR = 0x40000013
arm_tr_init_disas_context: dc->user = 0, dc->mmu_idx = 26, dc->current_el = 3
msr_mask: flags 00000001
          valid mask = ff1fffff (features=1fa71ef979)
         = mask 000000df
trans_MSR_reg: mask 000000df
gen_set_psr: mask 000000df
gen_set_cpsr: mask 000000df
0x00001000: mrs      r6, apsr
0x00001004: bic      r6, r6, #0x1f
0x00001008: orr      r6, r6, #0x10
0x0000100c: msr      cpsr_c, r6
cpsr_write: 40000010, mask 000000df
AArch32 mode switch from svc to usr PC 0x100c
arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0
0x00001010: svc      #0x20016
--- Reached SWI at 0x00001010 (0x20016)
OS_EnterOS emulation
cpsr_write: 60000013, mask ffffffff
cpsr_write: 40000010, mask ffffffff
AArch32 mode switch from svc to usr PC 0x1014
cpsr_write: 60000013, mask ffffffff
Should now be in SVC32
Run code at 0x00001014
Registers: 
   r0 : 0x00000000   r1 : 0x00000000   r2 : 0x00000000   r3 : 0x00000000
   r4 : 0x00000000   r5 : 0x00000000   r6 : 0x40000010   r7 : 0x00000000
   r8 : 0x00000000   r9 : 0x00000000  r10 : 0x00000000  r11 : 0x00000000
  r12 : 0x00000000   sp : 0x12345678   lr : 0x00102220   pc : 0x00001014
  CPSR = 0x60000013
  SPSR = 0x40000013
arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0
msr_mask: flags 00000001
          valid mask = ff1fffff (features=1fa71ef979)
          In user mode (no priviledge changes)
         = mask 00000000
trans_MSR_reg: mask 00000000
gen_set_psr: mask 00000000
gen_set_cpsr: mask 00000000
0x00001014: msr      cpsr_c, r6
cpsr_write: 40000010, mask 00000000
arm_tr_init_disas_context: dc->user = 0, dc->mmu_idx = 26, dc->current_el = 3
0x00001018: mov      r0, r0
0x0000101c: svc      #0x11
--- Reached SWI at 0x0000101c (0x11)
--- Should be in USR32, with R13 = 0x00010000, R14 = 0x0001234
Registers: 
   r0 : 0x00000000   r1 : 0x00000000   r2 : 0x00000000   r3 : 0x00000000
   r4 : 0x00000000   r5 : 0x00000000   r6 : 0x40000010   r7 : 0x00000000
   r8 : 0x00000000   r9 : 0x00000000  r10 : 0x00000000  r11 : 0x00000000
  r12 : 0x00000000   sp : 0x12345678   lr : 0x00102220   pc : 0x00001020
  CPSR = 0x60000013
  SPSR = 0x40000013

At the end here we should be in USR32, but the MSR has been rejected because it thinks we're in USR32.

Successful output (Unicorn 1)

This doesn't have my extra debug from inside unicorn:

Testing under Unicorn : (1, 0, 256L)
Header version: (1, 0, 2)
Changing ARM modes
--- Should be in SVC32, with R13 = 0x12345678, R14 = 0x00102220
Registers: 
   r0 : 0x00000000   r1 : 0x00000000   r2 : 0x00000000   r3 : 0x00000000
   r4 : 0x00000000   r5 : 0x00000000   r6 : 0x00000000   r7 : 0x00000000
   r8 : 0x00000000   r9 : 0x00000000  r10 : 0x00000000  r11 : 0x00000000
  r12 : 0x00000000   sp : 0x12345678   lr : 0x00102220   pc : 0x00000000
  CPSR = 0x40000013
  SPSR = 0x40000013
--- Should be in USR32, with R13 = 0x00010000, R14 = 0x0001234
Registers: 
   r0 : 0x00000000   r1 : 0x00000000   r2 : 0x00000000   r3 : 0x00000000
   r4 : 0x00000000   r5 : 0x00000000   r6 : 0x00000000   r7 : 0x00000000
   r8 : 0x00000000   r9 : 0x00000000  r10 : 0x00000000  r11 : 0x00000000
  r12 : 0x00000000   sp : 0x00010000   lr : 0x00001234   pc : 0x00000000
  CPSR = 0x40000010
  SPSR = 0x00000000
Could not turn on debug mask: 'Uc' object has no attribute 'ctl_set_debug_mask'
--- Executing code at 0x00001000
Run code at 0x00001000
Registers: 
   r0 : 0x00000000   r1 : 0x00000000   r2 : 0x00000000   r3 : 0x00000000
   r4 : 0x00000000   r5 : 0x00000000   r6 : 0x00000000   r7 : 0x00000000
   r8 : 0x00000000   r9 : 0x00000000  r10 : 0x00000000  r11 : 0x00000000
  r12 : 0x00000000   sp : 0x12345678   lr : 0x00102220   pc : 0x00001000
  CPSR = 0x40000013
  SPSR = 0x40000013
0x00001000: mrs      r6, apsr
0x00001004: bic      r6, r6, #0x1f
0x00001008: orr      r6, r6, #0x10
0x0000100c: msr      cpsr_c, r6
0x00001010: svc      #0x20016
--- Reached SWI at 0x00001010 (0x20016)
OS_EnterOS emulation
Should now be in SVC32
Run code at 0x00001014
Registers: 
   r0 : 0x00000000   r1 : 0x00000000   r2 : 0x00000000   r3 : 0x00000000
   r4 : 0x00000000   r5 : 0x00000000   r6 : 0x40000010   r7 : 0x00000000
   r8 : 0x00000000   r9 : 0x00000000  r10 : 0x00000000  r11 : 0x00000000
  r12 : 0x00000000   sp : 0x12345678   lr : 0x00102220   pc : 0x00001014
  CPSR = 0x60000013
  SPSR = 0x40000013
0x00001014: msr      cpsr_c, r6
0x00001018: mov      r0, r0
0x0000101c: svc      #0x11
--- Reached SWI at 0x0000101c (0x11)
--- Should be in USR32, with R13 = 0x00010000, R14 = 0x0001234
Registers: 
   r0 : 0x00000000   r1 : 0x00000000   r2 : 0x00000000   r3 : 0x00000000
   r4 : 0x00000000   r5 : 0x00000000   r6 : 0x40000010   r7 : 0x00000000
   r8 : 0x00000000   r9 : 0x00000000  r10 : 0x00000000  r11 : 0x00000000
  r12 : 0x00000000   sp : 0x00010000   lr : 0x00001234   pc : 0x00001020
  CPSR = 0x60000010
  SPSR = 0x00000000

And as you can see at the end we've got USR32 mode successfully.

Hopefully that shorter repro code will be more useful!

@wtdcode
Copy link
Member

wtdcode commented Jan 11, 2022

I'm getting some clue. The reason is TB caching. Could you try to clear all possible TB cache and try again?

@wtdcode
Copy link
Member

wtdcode commented Jan 11, 2022

Right I have a repro case in slightly fewer lines:

#!/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>

import sys
import struct

import capstone

import unicorn
from unicorn import (
        uc_version,
        UcError,
        Uc,
        UC_ARCH_ARM, UC_MODE_ARM,
        UC_ERR_EXCEPTION,
        UC_VERSION_MAJOR, UC_VERSION_MINOR, UC_VERSION_EXTRA)
from unicorn.arm_const import (UC_ARM_REG_R0,
        UC_ARM_REG_R1,
        UC_ARM_REG_R2,
        UC_ARM_REG_R3,
        UC_ARM_REG_R4,
        UC_ARM_REG_R5,
        UC_ARM_REG_R6,
        UC_ARM_REG_R7,
        UC_ARM_REG_R8,
        UC_ARM_REG_R9,
        UC_ARM_REG_R10,
        UC_ARM_REG_R11,
        UC_ARM_REG_R12,
        UC_ARM_REG_R13,
        UC_ARM_REG_R14,
        UC_ARM_REG_R15,
        UC_ARM_REG_SP,
        UC_ARM_REG_LR,
        UC_ARM_REG_PC,
        UC_ARM_REG_CPSR,
        UC_ARM_REG_SPSR)

reg_map = [
        UC_ARM_REG_R0,
        UC_ARM_REG_R1,
        UC_ARM_REG_R2,
        UC_ARM_REG_R3,
        UC_ARM_REG_R4,
        UC_ARM_REG_R5,
        UC_ARM_REG_R6,
        UC_ARM_REG_R7,
        UC_ARM_REG_R8,
        UC_ARM_REG_R9,
        UC_ARM_REG_R10,
        UC_ARM_REG_R11,
        UC_ARM_REG_R12,
        UC_ARM_REG_SP,
        UC_ARM_REG_LR,
        UC_ARM_REG_PC,
    ]
arm_names = [
        'r0', 'r1', 'r2', 'r3',
        'r4', 'r5', 'r6', 'r7',
        'r8', 'r9', 'r10', 'r11',
        'r12', 'sp', 'lr', 'pc'
    ]


ADDRESS = 0x1000
ARM_INSTS = """
E10F6000 : MRS     r6, apsr
E3C6601F : BIC     r6, r6, #&1F
E3866010 : ORR     r6, r6, #&10            ; switch to USR32
E121F006 : MSR     cpsr_c, r6
EF020016 : SWI     OS_EnterOS              ; marker for our failing code
E121F006 : MSR     cpsr_c, r6              ; return to USR32
E1A00000 : MOV     r0,r0
EF000011 : SWI     OS_Exit
"""
ARM_WORDS = []
for line in ARM_INSTS.splitlines():
    if ' ' not in line or line.startswith('#'):
        continue
    word = line.split(' ')[0]
    value = int(word, 16)
    ARM_WORDS.append(struct.pack('<L', value))
ARM_CODE = b''.join(ARM_WORDS)

# Keep a copy of the code we're running.
with open('code', 'wb') as fh:
    fh.write(ARM_CODE)

def dump_registers(uc):
    print("Registers: ")
    for rn in range(0, 16):
        value = uc.reg_read(reg_map[rn])
        sys.stdout.write("  %3s : 0x%08x" % (arm_names[rn], value))
        if rn % 4 == 3:
            sys.stdout.write("\n")
    print("  CPSR = 0x{:08x}".format(uc.reg_read(UC_ARM_REG_CPSR)))
    print("  SPSR = 0x{:08x}".format(uc.reg_read(UC_ARM_REG_SPSR)))


def read_word(uc, address):
    data = uc.mem_read(address, 4)
    word = data[0] | (data[1]<<8) | (data[2]<<16) | (data[3]<<24)
    return word


def diss(codeword, address):
    for i in md.disasm(codeword, address):
        print("0x%08x: %-8s %s" %(i.address, i.mnemonic, i.op_str))

def hook_code(uc, address, size, user_data):
    codeword = uc.mem_read(address, 4)
    diss(codeword, address)

md = capstone.Cs(capstone.CS_ARCH_ARM, capstone.CS_MODE_ARM)
md.detail = True
md.syntax = capstone.CS_OPT_SYNTAX_NOREGNAME


# 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("Changing ARM modes")
    try:
        # Initialize emulator in ARM mode
        mu = Uc(UC_ARCH_ARM, UC_MODE_ARM)

        # initialize machine registers in different modes
        mu.reg_write(UC_ARM_REG_CPSR, 0x40000013)   # Current mode = SVC32 mode
        mu.reg_write(UC_ARM_REG_SPSR, 0x40000013)   # Saved mode = SVC32 mode
        mu.reg_write(UC_ARM_REG_R13, 0x12345678)    # SVC stack value
        mu.reg_write(UC_ARM_REG_R14, 0x00102220)    # SVC link value
        print("--- Should be in SVC32, with R13 = 0x12345678, R14 = 0x00102220")
        dump_registers(mu)

        mu.reg_write(UC_ARM_REG_CPSR, 0x40000010)   # Current mode = USR32 mode
        mu.reg_write(UC_ARM_REG_R13, 0x0010000)     # USR stack value
        mu.reg_write(UC_ARM_REG_R14, 0x0001234)     # USR link value
        print("--- Should be in USR32, with R13 = 0x00010000, R14 = 0x0001234")
        dump_registers(mu)

        # Now the actual test, we're in SVC mode and we are going to execute some code
        mu.reg_write(UC_ARM_REG_CPSR, 0x40000013)   # Current mode = SVC32 mode

        mu.mem_map(ADDRESS, 4096)                   # 4K at the execution address
        mu.mem_write(ADDRESS, ARM_CODE)

        uc2 = mu
        uc2.hook_add(unicorn.UC_HOOK_CODE, hook_code)
        try:
            uc2.ctl_set_debug_mask(0xFFFFFFFF)
        except Exception as exc:
            print("Could not turn on debug mask: %s" % (exc,))

        print("--- Executing code at 0x{:08x}".format(ADDRESS))
        mu.reg_write(UC_ARM_REG_R15, ADDRESS)
        while True:
            pc = mu.reg_read(UC_ARM_REG_R15)
            print("Run code at 0x{:08x}".format(pc))
            dump_registers(mu)
            try:
                mu.emu_start(pc, ADDRESS + len(ARM_CODE))
            except UcError as e:
                if e.errno != UC_ERR_EXCEPTION:
                    raise
                # This is an exception call, so we want to process the SWI call.
                pc = mu.reg_read(UC_ARM_REG_R15) - 4
                word = read_word(mu, pc)
                swi_number = word & 0xFFFFFF
                print("--- Reached SWI at 0x{:08x} (0x{:x})".format(pc, swi_number))
                if swi_number == 0x11:
                    # OS_Exit - stop looping
                    break
                if swi_number == 0x20016:
                    # OS_EnterOS
                    print("OS_EnterOS emulation")

                    # This is the section of code from our failing program:
                    # I've commented out the sections of the code that don't seem to matter.

                    # >>>> Failing code
                    #value_40543 = uc2.mem_read(0, 32)  # bytearray(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
                    #value_40544 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_PC)  # 0x70178e0
                    #value_40545 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_PC)  # 0x70178e0
                    #value_40546 = uc2.mem_read(0x70178dc, 4)  # bytearray(b'\x16\x00\x02\xef')
                    #value_40547 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_PC)  # 0x70178e0
                    #value_40548 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_CPSR)  # 0x60000010
                    #value_40549 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_CPSR)  # 0x60000010
                    value_40550 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_CPSR)  # 0x60000010
                    uc2.reg_write(unicorn.arm_const.UC_ARM_REG_CPSR, 0x60000013)  # None
                    #value_40551 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_CPSR)  # 0x60000013
                    #uc2.reg_write(unicorn.arm_const.UC_ARM_REG_CPSR, value_40551)  # None
                    #value_40552 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_CPSR)  # 0x60000013
                    #uc2.reg_write(unicorn.arm_const.UC_ARM_REG_CPSR, value_40552)  # None
                    #value_40553 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_CPSR)  # 0x60000013
                    #uc2.reg_write(unicorn.arm_const.UC_ARM_REG_CPSR, value_40553)  # None
                    value_40554 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_CPSR)  # 0x60000013
                    uc2.reg_write(unicorn.arm_const.UC_ARM_REG_CPSR, value_40550)  # None
                    value_40555 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_CPSR)  # 0x60000010
                    uc2.reg_write(unicorn.arm_const.UC_ARM_REG_CPSR, value_40554)  # None
                    #value_40556 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_PC)  # 0x70178e0
                    #value_40557 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_CPSR)  # 0x60000013
                    #value_40558 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_CPSR)  # 0x60000013
                    #value_40559 = uc2.mem_read(0, 32)  # bytearray(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
                    #value_40560 = uc2.reg_read(unicorn.arm_const.UC_ARM_REG_PC)  # 0x70178e0
                    print("Should now be in SVC32")
                    # <<<< End failing code

                else:
                    # Unrecognised SWI call
                    raise

        print("--- Should be in USR32, with R13 = 0x00010000, R14 = 0x0001234")
        dump_registers(mu)

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


if __name__ == '__main__':
    test_arm()

I've lifted the section I explained from the scripted code (I should have done this earlier!), and put it into one of my test harnesses. All the bits that aren't relevant have been commented out which leaves us with a short sequence that seems to go wrong.

In the python code, with the state set to USR32:

  • CPSR = SVC32
  • CPSR = USR32
  • CPSR = SVC32

And now we're in SVC32 according to CPSR, BUT the system thinks dc->current_el = 0, so we cannot change modes with MSR.

Failing output (Unicorn 2)

Testing under Unicorn : (2, 0, 33554437L)
Header version: (2, 0, 5)
Changing ARM modes
--- Should be in SVC32, with R13 = 0x12345678, R14 = 0x00102220
Registers: 
   r0 : 0x00000000   r1 : 0x00000000   r2 : 0x00000000   r3 : 0x00000000
   r4 : 0x00000000   r5 : 0x00000000   r6 : 0x00000000   r7 : 0x00000000
   r8 : 0x00000000   r9 : 0x00000000  r10 : 0x00000000  r11 : 0x00000000
  r12 : 0x00000000   sp : 0x12345678   lr : 0x00102220   pc : 0x00000000
  CPSR = 0x40000013
  SPSR = 0x40000013
--- Should be in USR32, with R13 = 0x00010000, R14 = 0x0001234
Registers: 
   r0 : 0x00000000   r1 : 0x00000000   r2 : 0x00000000   r3 : 0x00000000
   r4 : 0x00000000   r5 : 0x00000000   r6 : 0x00000000   r7 : 0x00000000
   r8 : 0x00000000   r9 : 0x00000000  r10 : 0x00000000  r11 : 0x00000000
  r12 : 0x00000000   sp : 0x00010000   lr : 0x00001234   pc : 0x00000000
  CPSR = 0x40000010
  SPSR = 0x00000000
--- Executing code at 0x00001000
Run code at 0x00001000
Registers: 
   r0 : 0x00000000   r1 : 0x00000000   r2 : 0x00000000   r3 : 0x00000000
   r4 : 0x00000000   r5 : 0x00000000   r6 : 0x00000000   r7 : 0x00000000
   r8 : 0x00000000   r9 : 0x00000000  r10 : 0x00000000  r11 : 0x00000000
  r12 : 0x00000000   sp : 0x12345678   lr : 0x00102220   pc : 0x00001000
  CPSR = 0x40000013
  SPSR = 0x40000013
arm_tr_init_disas_context: dc->user = 0, dc->mmu_idx = 26, dc->current_el = 3
msr_mask: flags 00000001
          valid mask = ff1fffff (features=1fa71ef979)
         = mask 000000df
trans_MSR_reg: mask 000000df
gen_set_psr: mask 000000df
gen_set_cpsr: mask 000000df
0x00001000: mrs      r6, apsr
0x00001004: bic      r6, r6, #0x1f
0x00001008: orr      r6, r6, #0x10
0x0000100c: msr      cpsr_c, r6
cpsr_write: 40000010, mask 000000df
AArch32 mode switch from svc to usr PC 0x100c
arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0
0x00001010: svc      #0x20016
--- Reached SWI at 0x00001010 (0x20016)
OS_EnterOS emulation
cpsr_write: 60000013, mask ffffffff
cpsr_write: 40000010, mask ffffffff
AArch32 mode switch from svc to usr PC 0x1014
cpsr_write: 60000013, mask ffffffff
Should now be in SVC32
Run code at 0x00001014
Registers: 
   r0 : 0x00000000   r1 : 0x00000000   r2 : 0x00000000   r3 : 0x00000000
   r4 : 0x00000000   r5 : 0x00000000   r6 : 0x40000010   r7 : 0x00000000
   r8 : 0x00000000   r9 : 0x00000000  r10 : 0x00000000  r11 : 0x00000000
  r12 : 0x00000000   sp : 0x12345678   lr : 0x00102220   pc : 0x00001014
  CPSR = 0x60000013
  SPSR = 0x40000013
arm_tr_init_disas_context: dc->user = 1, dc->mmu_idx = 23, dc->current_el = 0
msr_mask: flags 00000001
          valid mask = ff1fffff (features=1fa71ef979)
          In user mode (no priviledge changes)
         = mask 00000000
trans_MSR_reg: mask 00000000
gen_set_psr: mask 00000000
gen_set_cpsr: mask 00000000
0x00001014: msr      cpsr_c, r6
cpsr_write: 40000010, mask 00000000
arm_tr_init_disas_context: dc->user = 0, dc->mmu_idx = 26, dc->current_el = 3
0x00001018: mov      r0, r0
0x0000101c: svc      #0x11
--- Reached SWI at 0x0000101c (0x11)
--- Should be in USR32, with R13 = 0x00010000, R14 = 0x0001234
Registers: 
   r0 : 0x00000000   r1 : 0x00000000   r2 : 0x00000000   r3 : 0x00000000
   r4 : 0x00000000   r5 : 0x00000000   r6 : 0x40000010   r7 : 0x00000000
   r8 : 0x00000000   r9 : 0x00000000  r10 : 0x00000000  r11 : 0x00000000
  r12 : 0x00000000   sp : 0x12345678   lr : 0x00102220   pc : 0x00001020
  CPSR = 0x60000013
  SPSR = 0x40000013

At the end here we should be in USR32, but the MSR has been rejected because it thinks we're in USR32.

Successful output (Unicorn 1)

This doesn't have my extra debug from inside unicorn:

Testing under Unicorn : (1, 0, 256L)
Header version: (1, 0, 2)
Changing ARM modes
--- Should be in SVC32, with R13 = 0x12345678, R14 = 0x00102220
Registers: 
   r0 : 0x00000000   r1 : 0x00000000   r2 : 0x00000000   r3 : 0x00000000
   r4 : 0x00000000   r5 : 0x00000000   r6 : 0x00000000   r7 : 0x00000000
   r8 : 0x00000000   r9 : 0x00000000  r10 : 0x00000000  r11 : 0x00000000
  r12 : 0x00000000   sp : 0x12345678   lr : 0x00102220   pc : 0x00000000
  CPSR = 0x40000013
  SPSR = 0x40000013
--- Should be in USR32, with R13 = 0x00010000, R14 = 0x0001234
Registers: 
   r0 : 0x00000000   r1 : 0x00000000   r2 : 0x00000000   r3 : 0x00000000
   r4 : 0x00000000   r5 : 0x00000000   r6 : 0x00000000   r7 : 0x00000000
   r8 : 0x00000000   r9 : 0x00000000  r10 : 0x00000000  r11 : 0x00000000
  r12 : 0x00000000   sp : 0x00010000   lr : 0x00001234   pc : 0x00000000
  CPSR = 0x40000010
  SPSR = 0x00000000
Could not turn on debug mask: 'Uc' object has no attribute 'ctl_set_debug_mask'
--- Executing code at 0x00001000
Run code at 0x00001000
Registers: 
   r0 : 0x00000000   r1 : 0x00000000   r2 : 0x00000000   r3 : 0x00000000
   r4 : 0x00000000   r5 : 0x00000000   r6 : 0x00000000   r7 : 0x00000000
   r8 : 0x00000000   r9 : 0x00000000  r10 : 0x00000000  r11 : 0x00000000
  r12 : 0x00000000   sp : 0x12345678   lr : 0x00102220   pc : 0x00001000
  CPSR = 0x40000013
  SPSR = 0x40000013
0x00001000: mrs      r6, apsr
0x00001004: bic      r6, r6, #0x1f
0x00001008: orr      r6, r6, #0x10
0x0000100c: msr      cpsr_c, r6
0x00001010: svc      #0x20016
--- Reached SWI at 0x00001010 (0x20016)
OS_EnterOS emulation
Should now be in SVC32
Run code at 0x00001014
Registers: 
   r0 : 0x00000000   r1 : 0x00000000   r2 : 0x00000000   r3 : 0x00000000
   r4 : 0x00000000   r5 : 0x00000000   r6 : 0x40000010   r7 : 0x00000000
   r8 : 0x00000000   r9 : 0x00000000  r10 : 0x00000000  r11 : 0x00000000
  r12 : 0x00000000   sp : 0x12345678   lr : 0x00102220   pc : 0x00001014
  CPSR = 0x60000013
  SPSR = 0x40000013
0x00001014: msr      cpsr_c, r6
0x00001018: mov      r0, r0
0x0000101c: svc      #0x11
--- Reached SWI at 0x0000101c (0x11)
--- Should be in USR32, with R13 = 0x00010000, R14 = 0x0001234
Registers: 
   r0 : 0x00000000   r1 : 0x00000000   r2 : 0x00000000   r3 : 0x00000000
   r4 : 0x00000000   r5 : 0x00000000   r6 : 0x40000010   r7 : 0x00000000
   r8 : 0x00000000   r9 : 0x00000000  r10 : 0x00000000  r11 : 0x00000000
  r12 : 0x00000000   sp : 0x00010000   lr : 0x00001234   pc : 0x00001020
  CPSR = 0x60000010
  SPSR = 0x00000000

And as you can see at the end we've got USR32 mode successfully.

Hopefully that shorter repro code will be more useful!

Good catch! I would have a look tomorrow.

@gerph
Copy link
Contributor Author

gerph commented Jan 11, 2022

How do I clear the tb caches? It wasn't possible on Unicorn 1. Is that one of the ctl calls?

Should I be doing that whenever I write code to the core?

@wtdcode
Copy link
Member

wtdcode commented Jan 11, 2022

I'm getting some clue. The reason is TB caching. Could you try to clear all possible TB cache and try again?

A sidenote: In UC1, all tb caches are cleaned per emulation and that's probably the reason why you get this regression.

@gerph
Copy link
Contributor Author

gerph commented Jan 11, 2022

I wondered that - I felt that its performance was lower than I should expect due to many calls in and out of exceptions.

I presume that I probably need to do something with them whenever memory is directly modified, in the same way that I would do a cache flush on a real OS, or a TLB flush if I changed the page tables?

It's late here, so I shaln't be looking at this much more today... maybe tomorrow I'll know more.

@wtdcode
Copy link
Member

wtdcode commented Jan 11, 2022

I wondered that - I felt that its performance was lower than I should expect due to many calls in and out of exceptions.

I presume that I probably need to do something with them whenever memory is directly modified, in the same way that I would do a cache flush on a real OS, or a TLB flush if I changed the page tables?

It's late here, so I shaln't be looking at this much more today... maybe tomorrow I'll know more.

Try this diff and see whether it works for your project.

diff --git a/qemu/accel/tcg/translate-all.c b/qemu/accel/tcg/translate-all.c
index e48670eb..e18a07b1 100644
--- a/qemu/accel/tcg/translate-all.c
+++ b/qemu/accel/tcg/translate-all.c
@@ -1530,7 +1530,7 @@ tb_link_page(struct uc_struct *uc, TranslationBlock *tb, tb_page_addr_t phys_pc,
         h = tb_hash_func(phys_pc, tb->pc, tb->flags, tb->cflags & CF_HASH_MASK,
                          tb->trace_vcpu_dstate);
         tb->hash = h;   // unicorn needs this so it can remove this tb
-        qht_insert(uc, &uc->tcg_ctx->tb_ctx.htable, tb, h, &existing_tb);
+        //qht_insert(uc, &uc->tcg_ctx->tb_ctx.htable, tb, h, &existing_tb);
 
         /* remove TB from the page(s) if we couldn't insert it */
         if (unlikely(existing_tb)) {
diff --git a/qemu/softmmu/cpus.c b/qemu/softmmu/cpus.c
index 25568fac..10d14b0a 100644
--- a/qemu/softmmu/cpus.c
+++ b/qemu/softmmu/cpus.c
@@ -212,7 +212,9 @@ void resume_all_vcpus(struct uc_struct* uc)
     // at that address is to exit emulation, but not for the instruction there.
     // if we dont do this, next time we cannot emulate at that address
 
-    g_tree_foreach(uc->exits, uc_exit_invalidate_iter, (void*)uc);
+    // g_tree_foreach(uc->exits, uc_exit_invalidate_iter, (void*)uc);
+
+    tlb_flush(cpu);
 
     cpu->created = false;
 }

@gerph
Copy link
Contributor Author

gerph commented Jan 12, 2022

Try this diff and see whether it works for your project.

Sorry, still fails, I'm afraid.

@wtdcode
Copy link
Member

wtdcode commented Jan 14, 2022

Fixed in 6ed2214.

Thanks for your cooperation!

@wtdcode wtdcode closed this as completed Jan 14, 2022
@gerph
Copy link
Contributor Author

gerph commented Jan 14, 2022

Very quick test with my example binary shows that this is now working.

I'll put it through the full test suite shortly and see how it fares with to my regression tests. I'm hopeful!

@gerph
Copy link
Contributor Author

gerph commented Jan 15, 2022

I've finally got around to trying it out with the full test suite - that's running in the background and I'll follow up with any issues, but I'm not expecting a problem.

The reason I don't expect a problem is that I threw the full 'run the OS with the desktop components and some applications' manual test at it, and it worked fine.

Whilst it doesn't show much of Unicorn itself, there's a recording of it running with Unicorn 2 here: https://share.gerph.org/s/T2U7zR4yiXCdZyd

To be clear... the OS - system calls, and many of the extensions to the system - are implemented in Python, so all graphics that goes to the screen (and indeed the windows you see in the video) are all implemented in Python. But the rest is emulated ARM code. The Boot menu that starts up at the beginning of the menu is a C program in ARM, then we enter the desktop - the entire desktop implementation as ARM code, as well as the applications that are running - the task information window, the file explorer, and the game MineHunt are all ARM code, running nearly unmodified from their original versions.

And the system seems to be functioning identically to how it was working under Unicorn 1.

@gerph
Copy link
Contributor Author

gerph commented Jan 15, 2022

Tests completed...

-----------
Pass:    1814
Fail:       2
Crash:      0
Skip:       0
-----------
Total run:     1816
Pass ratio:   99.89 %
Fail ratio:    0.11 %

Both failures are pre-fetch failures:

  • One of them might be due to the change in how the exceptions are now re-raised from hooks. I'll look into this - it's likely that the silent eating of the exception in the past was a bug in my code that I can fix.
  • The other is a change in the PC reported after the instruction fetch. This is more odd. I think I'll report this separately - it might be more correct in the new implementation, I'm not sure.

@gerph
Copy link
Contributor Author

gerph commented Jan 15, 2022

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

No branches or pull requests

2 participants