ARM CPSR and conditional instructions - revisited
PATCH 1/1 ARM CPSR and conditional instructions - revisited
Hello,
I was having a problem emulating some ARM code that I don't have any right to modify, and which has its origins back in the early nineties. I tracked it down to incorrect flag handling and found Justin Fletcher's post of Thu, 23 Nov 2006 via Google. Since his fix doesn't completely correct the problem, I've developed the attached patch and a simple test program.
I haven't tested whether the flags are set correctly other than by the MSR instruction, but the code I'm emulating gets a lot further than it used to, which is a reasonable smoke test, and the modifications to the flag setting routines are reasonably minor.
I doubt the modifications will result in a significant change in speed since, while the _cc routines are a little more complicated, the op_test_ routines have only got simpler and ARM code only sets the flags when they will be checked, and they are checked at least as often as they are set.
This is a very useful project, thanks to everyone who has worked on it.
Regards,
Simon Willcocks
Signature · 3 lines
ROLF - The RISC OS Look and Feel on Linux. http://stoppers.drobe.co.uk http://ro-lookandfeel.blogspot.com/
/* QEMU ARM Flags test
*
* Copyright (c) 2008 Simon Willcocks
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/* This is a test of the op_test_* routines in target-arm/op.c.
*
* The unpatched qemu-0.9.1 doesn't allow the N and Z flags to be
* set simultaneously, although ARM processors allow it. *
* The code to be emulated is:
*
* mov r0, #16 ; 0x10 * subs r0, r0, #1 ; 0x1 * svcvs 0x00000000 * lsl r1, r0, #28 * msr CPSR_f, r1 * mov r2, #0 ; 0x0 * orreq r2, r2, #1 ; 0x1 * orrne r2, r2, #2 ; 0x2 * orrcs r2, r2, #16 ; 0x10 * orrcc r2, r2, #32 ; 0x20 * orrmi r2, r2, #256 ; 0x100 * orrpl r2, r2, #512 ; 0x200 * orrvs r2, r2, #4096 ; 0x1000 * orrvc r2, r2, #8192 ; 0x2000 * orrhi r2, r2, #65536 ; 0x10000 * orrls r2, r2, #131072 ; 0x20000 * orrge r2, r2, #1048576 ; 0x100000 * orrlt r2, r2, #2097152 ; 0x200000 * orrgt r2, r2, #16777216 ; 0x1000000 * orrle r2, r2, #33554432 ; 0x2000000 * svc 0x00000000 * b 0xbfe115c8
*
* The cpu_arm_exec routine exits for the caller to implement the svc (SWI)
* instructions (when executed), and continues after them when the routine is
* called again.
*
* The value of r2 on exit should be a leading zero and a string of 1 and 2s. */
/* Build library with:
*
* ./configure --disable-kqemu --disable-system --target-list=arm-linux-user \
* --disable-sdl --disable-gfx-check --disable-vnc-tls && make
*
* Compile this file with:
*
* gcc flags_test1.c -o flags_test1 qemu-0.9.1/arm-linux-user/libqemu.a \
* -I qemu-0.9.1 -DNEED_CPU_H -Iqemu-0.9.1/fpu -I qemu-0.9.1/arm-linux-user/ \
* -Wall qemu-0.9.1/osdep.c -I qemu-0.9.1/target-arm/ -D_XOPEN_SOURCE=600
*
* The unpatched library outputs the following:
*
* bf86f878: 0000000f 01111112 NzCV
* bf86f878: 0000000e 02212112 NzCv
* bf86f878: 0000000d 01121122 NzcV
* bf86f878: 0000000c 02222122 Nzcv
* bf86f878: 0000000b 01111112 NzCV
* bf86f878: 0000000a 02212112 NzCv
* bf86f878: 00000009 01121122 NzcV
* bf86f878: 00000008 02222122 Nzcv
* bf86f878: 00000007 02221211 nZCV
* bf86f878: 00000006 02122211 nZCv
* bf86f878: 00000005 02221221 nZcV
* bf86f878: 00000004 02122221 nZcv
* bf86f878: 00000003 02211212 nzCV
* bf86f878: 00000002 01112212 nzCv
* bf86f878: 00000001 02221222 nzcV
* bf86f878: 00000000 01122222 nzcv
*
* Note that the first four lines are incorrect (although the conditional
* instructions are correct according to the stored flags).
*
* The patched library:
*
* bfe11618: 0000000f 02121111 NZCV
* bfe11618: 0000000e 02222111 NZCv
* bfe11618: 0000000d 02121121 NZcV
* bfe11618: 0000000c 02222121 NZcv
* bfe11618: 0000000b 01111112 NzCV
* bfe11618: 0000000a 02212112 NzCv
* bfe11618: 00000009 01121122 NzcV
* bfe11618: 00000008 02222122 Nzcv
* bfe11618: 00000007 02221211 nZCV
* bfe11618: 00000006 02122211 nZCv
* bfe11618: 00000005 02221221 nZcV
* bfe11618: 00000004 02122221 nZcv
* bfe11618: 00000003 02211212 nzCV
* bfe11618: 00000002 01112212 nzCv
* bfe11618: 00000001 02221222 nzcV
* bfe11618: 00000000 01122222 nzcv
*
*/
#include <stdio.h>
#include <stdint.h>
#include "cpu.h"
static const char *flag_string[16] = {
"nzcv", "nzcV", "nzCv", "nzCV", "nZcv", "nZcV", "nZCv", "nZCV",
"Nzcv", "NzcV", "NzCv", "NzCV", "NZcv", "NZcV", "NZCv", "NZCV" };int main()
{
uint32_t code[] = { 0xE3A00010, 0xE2500001, 0x6F000000, 0xE1A01E00,
0xE128F001, 0xE3A02000, 0x3822001, 0x13822002,
0x23822010, 0x33822020, 0x43822C01, 0x53822C02,
0x63822A01, 0x73822A02, 0x83822801, 0x93822802,
0xA3822601, 0xB3822602, 0xC3822401, 0xD3822402,
0xef000000, 0xEAFFFFEA };
struct CPUARMState *state = cpu_arm_init( "any" );
state->user_mode_only = 1;
cpu_set_log_filename("cpu_log.txt");
cpu_set_log(0xfff);
//state->singlestep_enabled = 1;
state->regs[15] = (uint32_t) code;
while ((cpu_arm_exec( state ) == EXCP_SWI && (state->regs[0] & 0x8000000) == 0)) {
printf( "%08x: %08x %08x %s\n", state->regs[15], state->regs[0], state->regs[2], flag_string[(cpsr_read( state ) >> 28 )] );
}
return 0;
}target-arm/cpu.h +15 -9
@@ -84,9 +84,17 @@uint32_t fiq_regs[5 ];/* cpsr flag cache for faster execution */ +/* All the NZCV flags may be set independently (MSR cpsr_f, #0xe0000000 works) */ +/* All the flags are zero or one; the flags are calculated less often than they are +* read, since ARM code only sets flags when it knows they will be read and often +* they are read more than once. */ +/* I note an assumption that in boolean true is the value 1. i.e. (0 == 0) == 1. */ +/* TODO: Establish whether storing the flags as 8 bit values is more efficient */ +uint32_t NF ;/* 0 or 1 */ +uint32_t ZF ;/* 0 or 1 */ uint32_t CF ;/* 0 or 1 */ -uint32_t VF ;/* V is the bit 31. All other bits are undefined */ -uint32_t NZF ;/* N is bit 31. Z is computed from NZF */ +uint32_t VF ;/* 0 or 1 */ +uint32_t QF ;/* 0 or 1 */ uint32_t GE ;/* cpsr[19:16] */ int thumb;/* cprs[5]. 0 = arm mode, 1 = thumb mode. */
@@ -255,10 +263,8 @@/* Return the current xPSR value. */ static inline uint32_t xpsr_read (CPUARMState *env) { -int ZF ; -ZF = (env->NZF ==0 ); -return (env->NZF &0x80000000 ) | (ZF <<30 ) - | (env->CF <<29 ) | ((env->VF &0x80000000 ) >>3 ) | (env->QF <<27 ) +return (env->NF <<31 ) | (env->ZF <<30 ) + | (env->CF <<29 ) | (env->VF <<28 ) | (env->QF <<27 ) | (env->thumb <<24 ) | ((env->condexec_bits &3 ) <<25 ) | ((env->condexec_bits &0xfc ) <<8 ) | env->v7m.exception;
@@ -267,11 +273,11 @@/* Set the xPSR. Note that some bits of mask must be all-set or all-clear. */ static inline void xpsr_write (CPUARMState *env,uint32_t val,uint32_t mask) { -/* NOTE: N = 1 and Z = 1 cannot be stored currently */ if (mask &CPSR_NZCV ) { - env->NZF = (val &0xc0000000 ) ^0x40000000 ; + env->NF = (val >>31 ); + env->ZF = (val >>30 ) &1 ; env->CF = (val >>29 ) &1 ; - env->VF = (val <<3 ) &0x80000000 ; + env->VF = (val >>28 ) &1 ; }if (mask &CPSR_Q ) env->QF = ((val &CPSR_Q ) !=0 );
target-arm/helper.c +5 -7
@@ -275,10 +275,8 @@uint32_t cpsr_read (CPUARMState *env) { -int ZF ; -ZF = (env->NZF ==0 ); -return env->uncached_cpsr | (env->NZF &0x80000000 ) | (ZF <<30 ) | - (env->CF <<29 ) | ((env->VF &0x80000000 ) >>3 ) | (env->QF <<27 ) +return env->uncached_cpsr | (env->NF <<31 ) | (env->ZF <<30 ) + | (env->CF <<29 ) | (env->VF <<28 ) | (env->QF <<27 ) | (env->thumb <<5 ) | ((env->condexec_bits &3 ) <<25 ) | ((env->condexec_bits &0xfc ) <<8 ) | (env->GE <<16 );
@@ -286,11 +284,11 @@void cpsr_write (CPUARMState *env,uint32_t val,uint32_t mask) { -/* NOTE: N = 1 and Z = 1 cannot be stored currently */ if (mask &CPSR_NZCV ) { - env->NZF = (val &0xc0000000 ) ^0x40000000 ; + env->NF = (val >>31 ) &1 ; + env->ZF = (val >>30 ) &1 ; env->CF = (val >>29 ) &1 ; - env->VF = (val <<3 ) &0x80000000 ; + env->VF = (val >>28 ) &1 ; }if (mask &CPSR_Q ) env->QF = ((val &CPSR_Q ) !=0 );
target-arm/op.c +36 -26
194 lines not on this page · raw message
Quoted 3 lines · Simon Willcocks
Hello,
I was having a problem emulating some ARM code
You need to use a newer qemu. This has been fixed for ages.
Paul
In message <[email protected]>
Quoted 6 lines · Paul Brook
Quoted 3 lines · Simon Willcocks
Hello,
I was having a problem emulating some ARM code
You need to use a newer qemu. This has been fixed for ages.
You're right; on the 1st April, I guess that makes me an April Fool :-)
On the other hand, it has raised a couple of questions.
1. At what point will there be a new release of the software?
Is there a target being worked towards?
2. How do I compile my simple test program?
This used to be good enough:
gcc flags_test1.c -o flags_test1 qemu-0.9.1/arm-linux-user/libqemu.a \
-I qemu-0.9.1 -DNEED_CPU_H -Iqemu-0.9.1/fpu -I qemu-0.9.1/arm-linux-user/ \
-Wall qemu-0.9.1/osdep.c -I qemu-0.9.1/target-arm/ -D_XOPEN_SOURCE=600
But now libqemu.a seems to need a lot of features from other places that I can't easily identify. (Just osdep.c covered everything, before.)
I can understand the emulator needing wrapped malloc-like routines, but why does the base emulator (which is what I thought the libqemu.a file contained) need to know about mmap_lock?
I'm a little frustrated, and inclined to go back to using the 0.9.1. library with my patch to continue, although there are presumably other improvements to the development build.
Thanks,
Simon
Signature · 3 lines
ROLF - The RISC OS Look and Feel on Linux. http://stoppers.drobe.co.uk http://ro-lookandfeel.blogspot.com/
Quoted 11 lines
2. How do I compile my simple test program?
This used to be good enough:
gcc flags_test1.c -o flags_test1 qemu-0.9.1/arm-linux-user/libqemu.a \ -I qemu-0.9.1 -DNEED_CPU_H -Iqemu-0.9.1/fpu -Iqemu-0.9.1/arm-linux-user/ \ -Wall
qemu-0.9.1/osdep.c-I qemu-0.9.1/target-arm/ -D_XOPEN_SOURCE=600But now libqemu.a seems to need a lot of features from other places that I can't easily identify.
Don't do that. libqemu has never really been usable as a standaline library.
Paul
Quoted 9 lines · In message <[email protected]> you wrote:
Quoted 9 lines
2. How do I compile my simple test program?
This used to be good enough:
gcc flags_test1.c -o flags_test1 qemu-0.9.1/arm-linux-user/libqemu.a \ -I qemu-0.9.1 -DNEED_CPU_H -Iqemu-0.9.1/fpu -Iqemu-0.9.1/arm-linux-user/ \ -Wall
qemu-0.9.1/osdep.c-I qemu-0.9.1/target-arm/ -D_XOPEN_SOURCE=600
Quoted 4 lines
Quoted 2 lines
But now libqemu.a seems to need a lot of features from other places that I can't easily identify.
Don't do that. libqemu has never really been usable as a standaline library.
Unfortunately, that's exactly how I want to use it!
What I have is a system, based on a Linux kernel, that behaves similarly to RISC OS; the OS developed by Acorn to run on their Acorn RISC Machine processors, later spun off to be Advanced RISC Machines Ltd..
I have tried to make it sufficiently similar that it should be relatively easy to map RISC OS system calls (especially the WIMP and font ones) to the ROLF interface, so that RISC OS programs can be ported to run on faster hardware. There's a new interface to the Wimp, which adds a couple of features so that people can make use of pre-emptive scheduling and asynchronous I/O from Linux but, at heart, is the cooperative mechanism from 1987 that still works very well indeed but is limited by the speed of ARM processors.
One feature that I would like to implement is the ability to run some RISC OS programs under ROLF (on i386), for which I need an emulator. The example I chose to start with was a vector graphics renderer for a program called ArtWorks, which requires some "Relocatable Modules" which have been
supported over the last sixeen years on RISC OS. (A Relocatable Module can
be any combination of a shared library, a device driver, a kernel extension module, or an application.)
I don't need to emulate the hardware of a RISC OS computer (nor would I want to; Acorn wrote operating systems with a good abstraction from the hardware from before MS were around, and programs generally used it). What I do need to do is convince old software that it is running on an ARM processor (that's where QEMU comes in) and interfacing with RISC OS when it makes a SWI call. To do the latter, I'm implementing a shared library that can provide a one-routine interface to run any* RISC OS system call, and trying to make those calls available to ARM code in an emulator as a way to get native RISC OS applications to run. The same library is callable from BASIC (built in to RISC OS and therefore commonly used for application development) via the Brandy basic interpreter.
As at qemu-0.9.1, that seemed to be relatively easy, now it seems to be harder, and I'm not sure why.
I could be wrong, but I think there is a movement from emulation of a particular processor to emulation of a particular processor running a particular OS, on a particular piece of hardware, which is probably very useful for many people, but isn't so much for me. I'd like the abstraction to allow me to extract the processor without any preconceptions; to be able to use QEMU as a tool, rather than being forced to integrate ROLF into QEMU.
* potentially; many won't ever be needed.
Signature · 3 lines
ROLF - The RISC OS Look and Feel on Linux. http://stoppers.drobe.co.uk http://ro-lookandfeel.blogspot.com/