Anyone try QEMU and use arm-none-eabi-gdb successfully?

Status
Not open for further replies.

wwg

Well-known member
While I am impatiently waiting for my teensy-3.1 to arrive, I've decided to give qemu-system-arm and arm-none-eabi-gdb commands a whirl, to try to get familiar with the ARM assembly instruction set. I thought this was going to be a slam-dunk, but it is turning out to be a challenge.

I took the teensy3 Blinky example and used the Arduino IDE to compile it so I could be confident that I had a "working" executable. It was a fun easter-egg hunt locating it's elf and hex files, but I did finally locate them (this is one of a few of those dumbing-down things I really hate about the Arduino setup). Before anyone mentions it, note that the shift-Verify/Compile trick does not work on the Mac version of the IDE.

It was suggested elsewhere that the -cpu cortex-a9 (or a8) would be most suitable for a cortex-m4 choice (up to cortex m3 is supported by qemu). My QEMU command was thus (under Mac OSX):

$ qemu-system-arm -cpu cortex-a9 -nographic -monitor null -serial null -semihosting -kernel ./blink.elf -gdb tcp::51234 -S

In a different session I started gdb:

$ arm-none-eabi-gdb ./blink.elf
GNU gdb (GNU Tools for ARM Embedded Processors) 7.6.0.20140228-cvs
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=x86_64-apple-darwin10 --target=arm-none-eabi".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /Users/ve3wwg/work/teensy3.1/blink.elf...done.
(gdb) target remote localhost:51234
Remote debugging using localhost:51234
0x00000000 in gVectors ()
(gdb) load
Loading section .text, size 0x3190 lma 0x0
Loading section .data, size 0x5b8 lma 0x3190
Start address 0x0, load size 14152
Transfer rate: 4606 KB/sec, 1769 bytes/write.
(gdb) info file
Symbols from "/Users/ve3wwg/work/teensy3.1/blink.elf".
Remote serial target in gdb-specific protocol:
Debugging a target over a serial line.
While running this, GDB does not access memory from...
Local exec file:
`/Users/ve3wwg/work/teensy3.1/blink.elf', file type elf32-littlearm.
Entry point: 0x0
0x00000000 - 0x00003190 is .text
0x1fff8000 - 0x1fff80a0 is .usbdescriptortable
0x1fff80a0 - 0x1fff8400 is .usbbuffers
0x1fff8400 - 0x1fff89b8 is .data
0x1fff89b8 - 0x1fff8d80 is .bss
(gdb)


The one disturbing thing here is that it says the Entry point is 0x0, which is where the vectors start:

(gdb) disas 0,10
Dump of assembler code from 0x0 to 0xa:
=> 0x00000000 <gVectors+0>: andcs r8, r0, r0
0x00000004 <gVectors+4>: ; <UNDEFINED> instruction: 0x000001bd
0x00000008 <gVectors+8>: andeq r0, r0, r9, lsl r6


Hmmm... ok, maybe this has partially answered my own question. There must be a "RESET" vector that the (real) CPU chooses to branch through. That must be what QEMU is not doing correctly (it never gets off the ground). I might be able to finagle this with a gdb call command.

But while I am here, I might as well ask if anyone else has had any experience running teensy-3.x code under QEMU? Any "secret sauce" to get this to work correctly?

Warren
 
$ qemu-system-arm -cpu cortex-a9 -nographic -monitor null -serial null -semihosting -kernel ./blink.elf -gdb tcp::51234 -S

In a different session I started gdb:

$ arm-none-eabi-gdb ./blink.elf
GNU gdb (GNU Tools for ARM Embedded Processors) 7.6.0.20140228-cvs
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=x86_64-apple-darwin10 --target=arm-none-eabi".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /Users/ve3wwg/work/teensy3.1/blink.elf...done.
(gdb) target remote localhost:51234
Remote debugging using localhost:51234
0x00000000 in gVectors ()
(gdb) load
Loading section .text, size 0x3190 lma 0x0
Loading section .data, size 0x5b8 lma 0x3190
Start address 0x0, load size 14152
Transfer rate: 4606 KB/sec, 1769 bytes/write.
(gdb) info file
Symbols from "/Users/ve3wwg/work/teensy3.1/blink.elf".
Remote serial target in gdb-specific protocol:
Debugging a target over a serial line.
While running this, GDB does not access memory from...
Local exec file:
`/Users/ve3wwg/work/teensy3.1/blink.elf', file type elf32-littlearm.
Entry point: 0x0
0x00000000 - 0x00003190 is .text
0x1fff8000 - 0x1fff80a0 is .usbdescriptortable
0x1fff80a0 - 0x1fff8400 is .usbbuffers
0x1fff8400 - 0x1fff89b8 is .data
0x1fff89b8 - 0x1fff8d80 is .bss
(gdb)

For posterity, I thought I'd add a couple of notes for anyone that might want to try this themselves.

The Cortex-M4 loads $sp from location 0x0, $pc from 0x04 and sets the $lr = 0xFFFFFFFF. Unfortunately this doesn't seem to happen automatically in QEMU (I'd love to learn why if anyone knows). I tried to work around it by setting the $pc manually, even using the gdb jump command to go to the right address. However, gdb insists on going to the entry point address if you try to step (si). As you can see in the session above, gdb thinks the entry point is address 0x0.

Through experimentation, I discovered a manual way around this problem:

1. Start qemu-system-arm and arm-none-eabi-gdb as outlined before.
2. (gdb) set $sp = *0 # Load SP from location 0
3. (gdb) set $pc = *4 # Load PC for displaying later from location 4..
4. (gdb) set $lr = -1 # Initialize LR as is normally done by the CPU (may not be essential)
5. (gdb) layout asm # Show the asm code that would be started next
6. (gdb) layout regs # Show registers also

Step 2 sets up the stack pointer from the vector at address zero.
Step 3 loads the program counter so that the layout asm can show you the code that should be starting next. Note the symbol shown at left:

>│0x1bc <ResetHandler> ldr r3, [pc, #312] ; (0x2f8 <ResetHandler+316>) │
│0x1be <ResetHandler+2> movw r2, #50464 ; 0xc520 │
│0x1c2 <ResetHandler+6> push {r4, lr} │
│0x1c4 <ResetHandler+8> strh r2, [r3, #0] │
│0x1c6 <ResetHandler+10> movw r2, #55592 ; 0xd928 │
│0x1ca <ResetHandler+14> strh r2, [r3, #0] │
│0x1cc <ResetHandler+16> nop │
│0x1ce <ResetHandler+18> nop │
│0x1d0 <ResetHandler+20> bl 0x620 <startup_default_early_hook> │
│0x1d4 <ResetHandler+24> ldr r3, [pc, #292] ; (0x2fc <ResetHandler+320>) │
│0x1d6 <ResetHandler+26> mov.w r2, #150994944 ; 0x9000000 │
│0x1da <ResetHandler+30> str r2, [r3, #0]

In my case (teensy-3.1 Blinky) the symbol is ResetHandler. Make note of this to be used again below.

7. Set breakpoint at symbol (ResetHandler): (gdb) b ResetHandler
8. Tell gdb where to go next: (gdb) jump ResetHandler
9. The breakpoint will now stop it's execution there so that you can single step/whatever from this point.

If you have "layout regs" enabled, you can watch the registers change as you single step through the code using the "si" gdb command.

From this point, you should be able to build asm snippets and get the simulator to run through your code. It's a lot of manual steps, but using a .gdbinit file you could automate the "setup".

The Secret Sauce:

I was nearly there but the "secret sauce" is that the "jump" command doesn't change gdb's sense of instruction address unless you give it a symbol. I tried everything possible numerically, including a jump *0x4, which works -- until you do the si command. The si command will return to the entry point even though you've manipulated the $pc with set or jump commands.

Also note, on the Mac I find that I need to make sure I kill off QEMU after I exit gdb, or the Mac's CPU gets very busy (and the fan starts to run).
 
I don't have time to look at this right now (or my other Teensy projects) but this is really interesting stuff! Thanks!

My gdb is rusty but doesn't "jump ResetHandler" take care of "set $pc = *4"? Assuming of course that the entry point in that image is called ResetHandler. I wonder if your "secret sauce" is really revealing a bug in the gdb remote module in QEMU.

What I'd really love to (have time to) do is get the non-Arduino build environment set up for T3.x and get gdb working with QEMU targets ("make debug-qemu" or some such), and maybe even with gdb remote controlling a real T3.

I know that the Arduino environment is a huge boon for getting started (and for quick protos) but when I spend more time with T3.x I'm sure going to miss being able to easily step out of it and go to raw c/c++ like I do with T2 and plain AVRs. But then I never had a working emulator or gdb for AVR either.
 
My gdb is rusty but doesn't "jump ResetHandler" take care of "set $pc = *4"? Assuming of course that the entry point in that image is called ResetHandler.

In effect, yes. If you know that is what it is going to be, then this is going to be easier. I just outlined the most general of procedures, so that you can take any executable and trace it through its startup. Further, if you put those steps into .gdbinit, then it will always do the Right Thing (TM).

I wonder if your "secret sauce" is really revealing a bug in the gdb remote module in QEMU.

If I get some time, I'm going to try this out under Linux with Linux code (OSX uses this stupid lldb instead for Mac). It might be intentional by gdb, since I can see that being useful for just examining things but not changing the flow. But I see this as a bug-- if you change $pc manually or through jump, then it should be respecting that from that point on.

What I'd really love to (have time to) do is get the non-Arduino build environment set up for T3.x and get gdb working with QEMU targets ("make debug-qemu" or some such), and maybe even with gdb remote controlling a real T3.

My teensy is still in the mail, so I invested some time to set up a proper "make environment" while I wait. This environment is on git here for anyone who's interested:

git@github.com:ve3wwg/teensy3_lib.git

I know there are a few others out there, but I prefer to keep mine very simple.

I know that the Arduino environment is a huge boon for getting started (and for quick protos) but when I spend more time with T3.x I'm sure going to miss being able to easily step out of it and go to raw c/c++ like I do with T2 and plain AVRs. But then I never had a working emulator or gdb for AVR either.

I know what you mean. I use MicroEmacs and make. Combine that with emacs command-line editing, I can get a lot done in a few keystrokes (and how I work fulltime at my job). No easter-egg hunts for this parameter or that in GUI forms etc. - just grep/search the Makefile for what you want and you're done.

I set my own Make structure up so that the Arduino environment is precompiled and put into a static libteensy3.a library. I also modified wiring.h to eliminate the annoying setup() and loop() refs, so I can use a proper main() program like a man. (Does a separate setup() and loop() function really make things easier for beginners???) I just #if-ed those refs out of wiring.h if -DNON_ARDUINO is defined in the Makefile. I do have an unresolved issue with usb_desc.c because it is designed to be recompiled every time by the Arduino toolchain. How it allocates pipes etc. is determined by the likes of -DUSB_SERIAL, which is different if other components are chosen. For now, I just define USB_SERIAL and will come back to this later when I have a better solution.

If you decide to create your own custom environment along the same lines, I'll offer this time saving info. I had everything building just fine but the hex files were wrong. There were never any reset vectors and gdb would complain there were no symbols. It should have been obvious, but I spun a lot of cycles determining why. I even tried using the Arduino compiler and the result was the same. In a nutshell, you need to force link in teensy3/mk20dx128.o (or your custom replacement) in order to define the ResetHandler and the start vectors. It seems obvious now, but at the time, this was most perplexing!

I use a Mac, so I initially installed QEMU from the mac ports collection. That prolly would be enough but because of some of the issues I was debugging, I decided later to install the latest from sources. Often there are build problems on a Mac but I must say it configured and built nicely. So certainly give QEMU a try.

I don't yet know how good the cortex-a8/a9 maps to the m4, but it seems ok so far. I only intend to use this for troubleshooting specialized asm routines. I want to get coroutines working on this big teensy rather than uglifying code to poll state in a big loop. If I later get more adventurous, I may implement pthreads. This host is big enough to do some "real" work along these lines.

And since I don't use the Arduino supplied toolchain, I had to make a small tweak to the mk20dx256.ld (I named as mk20dx256_sbrk.ld) load script. This provides the symbol "end" for _sbrk() in the launchpad.net toolchains (https://launchpad.net/gcc-arm-embedded). This allows _sbrk() to manage the heap, as I'm sure you're aware.

Just to stoke your appetite, I'll share another experiment. I did a simple build with a shell of a main() program, using:

- STL std::string
- STL std::stringstream
- STL std::unordered_map
- and a simple calculation involving double

to see how much the C++ library adds to the code. It totalled near 64k. But with the exciting prospect of having 256k flash to start with, this still leaves 75% of flash to fit a real C++ application in. The STL makes writing apps soooo much cleaner and enjoyable. Something you'd never shoe into an AVR host. The STL saves you from a lot of debugging and memory leaks too. Hence my excitement over this teensy-3.1!
 
Here's another arm QEMU tip that may be helpful:

When running actual Arduino library code in EMU, the ResetHandler() is not going to succeed in initialization because the hardware emulation is not there. But let's say you want to trace some asm code in the comfort of QEMU, to see what is going on, where no hardware is involved.

To bypass the hardware initialization in the ResetHandler(), simply add some code for the startup_early_hook() to invoke main()/setup()/loop() like this:

Code:
// C++
int
main(int argc,char **argv,char **envp) {
        ...
        return x;
}

extern "C" {
        void
        startup_early_hook() {
                main(0,0,0);
        }
}
If the code is C++, be sure to wrap the startup_early_hook() in an extern "C" block, or it will have no effect. Likewise omit the extern "C" when you are compiling C code.

Code:
/* C Code */
int
main(int argc,char **argv,char **envp) {
        ...
        return x;
}

void
startup_early_hook() {
        main(0,0,0);
}

Alternatively invoke your setup()/loop() functions from the startup_early_hook() if you are using the Ardunio styled startup sequence like this:

Code:
extern "C" {
        void
        startup_early_hook() {
                setup();
                for (;;) 
                    loop();
        }
}

Be aware that this "trick" also bypasses the following, which are normally invoked by the ResetHandler() (in mk20dx128.c):

- _init_Teensyduino_internal_() for mapping the i/o pins
- __libc_init_array()

Since you don't have the hardware in QEMU, then omitting _init_Teensyduino_internal_() is sensible (and likely required). Omitting __libc_init_array() call also appears ok AFAIK, since I believe it only applies to dynamic library use, which is not in use here.

So the short-circuit call from the startup_early_hook() to call main() (or something else) should get you going in QEMU.

Technically, you could probably get away with just performing a gdb "jump main" as you start your emulator (after setting $sp). I wanted to avoid that in case the future ResetHandler() performed additional critical initialization ahead of the startup_early_hook().
 
Last edited:
I thought I'd post some final conclusions on my own QEMU attempts here, to save others the trouble, or time.

I did manage to execute some code in QEMU with much foolery, monkeying with the ResetHandler etc. In the end, the big problem seems to be with the way that QEMU is handling RAM.

The main stumbling block was that yes, short-circuiting the ResetHandler (or doing your own) can get you going. But the one loop inside that code that copies the flash data to the RAM data areas, fails. You can even manually set those RAM locations with gdb, but those locations will always read back as zero. This has to be a "machine view" issue in QEMU. What is needed is a K20 machine view of the memory layout, with the flash and RAM addressed appropriately.

So while qemu-system-arm can still be useful for some rudimentary cortex-m4 (or nearly) simulation, you'll run into several obstacles that make it much less useful.
 
Well folks, it looks like I gave up too soon in my previous reply to myself. I looked at the QEMU source code and was able to work out how to create a "teensy-3.1" machine. I cloned from the Stellaris LM3S6965EVB machine (-machine lm3s6965evb), which uses the Cortex-m3, with the same SRAM and flash sizes. The biggest problem was that the static RAM was in the wrong place, which got fixed (yeah!).

So I cloned a module from QEMU's hw/arm/stellaris.c as hw/arm/teensy3.c. This gets 'teensy-3.1' defined as a 'machine'. I also started a clone of hw/arm/armv7m.c as armv7e.c for Cortex-m4 support, where I patched the starting address for the SRAM. This module essentially is still a Cortex-m3 in support level, but hopefully this can be changed over time. There are remnants of the Stellaris peripheral support in the teensy3 "machine" that I'll be removing in the future. But for now, those that need to trace through short runs of assembly code can now do it in the luxury of instant register gratification in the comfort of your MacBook Pro (etc.).

First on my to-do list is to figure out how to make the cpu reset properly through the vectors. This is really just an inconvenience since you can work around this with the gdb procedure provided below.

The teensy3 enhanced QEMU software can be obtained here:

https://github.com/ve3wwg/teensy3_qemu

Doing the usual ./configure, make and sudo make install, should get it built and installed for you.


Start QEMU in one session with your executable (use any port like 51234 below):

$ /usr/local/bin/qemu-system-arm -machine teensy-3.1 -nographic -monitor null -serial null -semihosting -kernel ./myarmprog.elf -gdb tcp::51234 -S

Start gdb in a second session (using same port):

$ arm-none-eabi-gdb --exec="myarmprog.elf" --se="myarmprog.elf" --ex="target remote localhost:51234" --ex "load" -ex "b main"

I recommend scripting above two commands.
 
Glad to hear that! I am busy with some AVR work at the moment, but some day need to get back to my teensy-3.1 projects.
 
Status
Not open for further replies.
Back
Top