qemu reports wrong info via SYS_HEAPINFO RDI monitor command to newlib startup code?

Asked by Tamas Kleiber

Hi,

 I have a problem which might be related to qemu, semihosting on arm or newlib or to my compiler settings or it could be just my ignorance... I try to run a small application in qemu v2.0.50.0, compiled with gcc-arm-none-eabi-4_7-2013q3-20130916 on windows using the semihosting example as basis for the compiler settings.

I invoke qemu using the following command line:

qemu-system-arm.exe -M lm3s6965evb -cpu cortex-m3 -nographic -monitor null -serial null -semihosting -kernel hello-CM3.elf -gdb tcp::1234,ipv4 -S

hello-CM3.elf is compiled and linked using the following compiler settings:

arm-none-eabi-gcc hello.c -mthumb -mcpu=cortex-m3 -Os -flto -ffunction-sections -fdata-sections --specs=nano.specs --specs=rdimon.specs -lc -lc -lrdimon -L. -T gcc.ld -Wl,--gc-sections -Wl,-Map=hello.map -o hello-CM3.alf

where gcc.ld is the default linker description file from the compiler's examples folder: "share/gcc-arm-none-eabi/samples/ldscripts/gcc.ld" copied to the semihost example application's folder with the following changes to the memory map:

MEMORY
{
  FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 256K
  RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
}

I use eclipse CDT's embedded hardware debugger plug-in and the compiler's arm-none-eabi-gdb tool to debug the application via qemu. I can successfully connect to qemu from eclipse and I am able to debug the start-up code of the compiled application in assembler mode right from the entry point. The stack and the application entry point is read and configured by qemu from the elf executable image.

The start-up code of newlib(-nano) implemented in crt0.S (.../gcc-arm-none-eabi-4_7-2013q3-20130916/src/newlib-nano-1.0/libgloss/arm/crt0.S) using the RDI configuration settings (rdimon-crt0.o) acts in a way that right after entering the _start() function the angel interface command SYS_HEAPINFO is called (AngelSWI_Reason_HeapInfo) which reports the following information back from qemu to the start-up code:
__heap_base: 0x4000000
__heap_limit: 0x8000000
__stack_base: 0x8000000
__stack_limit: 0x0000000

The above information is somewhat hard-coded into qemu's arm_semi.c module. I have tried other machine types too and I always get this information whatever I tried...

Based on the above information the stack pointer (SP) is set to 0x8000000 by the start-up code and this is where my problem starts... according to my knowledge, the available RAM memory area of the lm3s6965evb qemu machine type is 64K only and the RAM address space starts at 0x20000000. In eclipse I can modify the memory area at 0x20000000. I can write new values and read them back. When the first function is entered by the start-up code which uses the stack, called initialise_monitor_handles(), actually qemu fails to write the data to the stack residing at address 0x8000000 and closely below - the data remains to be read back as 0x00 (in eclipse the address space is read as 0x00, but the memory cannot be written to). So when the initialise_monitor_handles() function's epilogue section tries to restore the saved hardware registers (ldmia.w sp!, {r4, r5, r6, r7, r8, r9, pc}) it restores 0x00 to all of them which ends up in a code runaway...

Why qemu reports to my application those hard-coded heap and stack memory pool data when they does not really exist? Why does newlib's start-up code set a new stack pointer based on SYS_HEAPINFO when it already has a valid value configured into it by the reset sequence?
Am I missing or misunderstanding something? Do I have to configure qemu in a different way?
Is there a way to figure out via GDB or via qemu command line parameters the actual memory map of the simulated machine?

Based on armv7m_init() in qemu's source code I would say that my linker settings are correct for my application.

Any help would be very welcome, thank you in advance for your kind support.

Best regards,
 Tamas

_

Question information

Language:
English Edit question
Status:
Solved
For:
GNU Arm Embedded Toolchain Edit question
Assignee:
No assignee Edit question
Solved by:
Tamas Kleiber
Solved:
Last query:
Last reply:
Revision history for this message
Tamas Kleiber (kleiber-tamas) said :
#1

Hi,

 I have received the following response from the maintainer of qemu's ARM system:

---------------------
The SYS_HEAPINFO code is very simplistic for bare-metal mode:
it just returns [ram_size / 2, ram_size, ram_size, 0], regardless of
which machine model you're using. This works if the machine has
RAM starting at address zero and the ram_size variable actually
corresponds to the amount of RAM being modelled, but not otherwise.
Unfortunately for the Cortex-M3 board models neither of these is true,
and so the SYS_HEAPINFO results are not very useful.

> Could you please help me figure out what could be the issue?

As a cheesy workaround you may be able to play with the -m argument
to QEMU which tries to set the RAM size. I think the Stellaris board
models will ignore what you request when deciding how much RAM
to create, but the value will affect the results of the SYS_HEAPINFO
call, so you may be able to get it to produce usable results by
tweaking the argument to -m. This is definitely a bit of a hack that
might conceivably break in the future, though.

Otherwise I think you'll need to write your bare-metal code to avoid
using the results of SYS_HEAPINFO. It already has to know where
RAM is, because the initial executable has to be linked to be loaded
into RAM. So there's no extra board-dependency that you didn't
already have. And the linker can give a better value for heap_base
as well, since it knows what the highest address of the executable
and BSS is, whereas QEMU just guesses that the executable is
using less than half the RAM.
---------------------

Did anyone work around this problem?

Revision history for this message
Tamas Kleiber (kleiber-tamas) said :
#2

The problem is related to the Cortex-M family of MCUs/boards supported by QEMU and to the tool's semi-hosting implementaiton.

By modifying QEMU sources I could resolve all my issues.

Revision history for this message
franchan (francis-meyvis) said :
#3

Hello Mr Kleiber,

What did you have to change for semihosting to work on qemu?
Could you share the changes?

I was trying as well with qemu
- with newlib like you but that did not work on qemu (it does on my target connected through openocd)
- with a function based on BRKP but this does not work neither

static inline int __semihost(int reason, const void *arg) {
    int value;

    asm volatile (
       "mov r0, %1" "\n\t"
       "mov r1, %2" "\n\t"
       AngelSWIInsn " %a3" "\n\t"
       "mov %0, r0"
       : "=r" (value) /* output operands */
       : "r" (reason), "r" (arg), "i" (AngelSWI) /* input operands */
       : "r0", "r1", "r2", "r3", "ip", "lr", "memory", "cc" /* list of clobbered registers */
    );

    return value;
}

Revision history for this message
franchan (francis-meyvis) said :
#4

Ive used __semihost as defined in semihost_api.h and semihost_api.c
(from https://github.com/mbedmicro/mbed.git)
I've these conculsions so far

 - Semihosting works if not using gdb and adding -semihosting to qemu
   The output is shown in qemu
   - from printf() (when linked with the -lrdimon)
   - from __semihost() (even without -lrdimon)
 - Semihosting does not work when gdb controls qemu
   qemu without -semihosting then printf() halts the debugger
 - On a real target with openocd
   in gdb "mon arm semihosting enable"
   - printf() causes output in openocd
   - __semihost() causes output in openocd and halts

Perhaps it does not relate completely to newlib but
might help someone google for gdb/qemu/newlib and semihosting on cortex-m3

Revision history for this message
Tamas Kleiber (kleiber-tamas) said :
#5

Hi franchan,

 I am using a qemu branch which implements an EFM32 giant gecko board from Silabs (Energy Micro). It can be found on github. The CPU core is an ARM Cortex-M3. I am building it my self on Windows 7 x64 using MinGW 4.8.1-4 together with msys.

I am using the GNU Tools for ARM Embedded Processors compiler from this place and its arm-none-eabi-gdb client to connect to qemu running my bare-metal application.

The only code I had to change in that EFM32 qemu branch is arm-semi.c which implements the angel interface for semihosting which is supported by libgloss/newlib for arm-v7-m. Fortunately the GNU Tools for ARM Embedded Processors compiler uses newlib and newlib-nano so everything is in place. In the share folder there are also examples showing how to set up the compiler to build for semihosting.

In particular my LD flags for the linker: --specs=nano.specs --specs=rdimon.specs -lc -lrdimon .

The changes in arm-semi.c are related to information which is not available if you do not have a real kernel... e.g. ts->boot_info is not even populated, it is NULL on my bare metal setup. Then the limit setting is a default hard-coded value which of course is not valid for the loaded target machine's memory layout and for the loaded application which is linked to a different address range which is implemented by the target EFM32 machine:

diff arm-semi_new.c arm-semi.c
439,441c439,441
< output_size = (ts->boot_info) ? strlen(ts->boot_info->kernel_filename)
< + 1 /* Separating space. */ : 0
< + (ts->boot_info) ? strlen(ts->boot_info->kernel_cmdline) : 0
---
> output_size = strlen(ts->boot_info->kernel_filename)
> + 1 /* Separating space. */
> + strlen(ts->boot_info->kernel_cmdline)
473c473
< pstrcpy(output_buffer, output_size, ts->boot_info ? ts->boot_info->kernel_filename : " ");
---
> pstrcpy(output_buffer, output_size, ts->boot_info->kernel_filename);
475c475
< pstrcat(output_buffer, output_size, ts->boot_info ? ts->boot_info->kernel_cmdline : " ");
---
> pstrcat(output_buffer, output_size, ts->boot_info->kernel_cmdline);
545,548c545,548
< ptr[0] = tswap32(0x20001be0);
< ptr[1] = tswap32(0x200033e0);
< ptr[2] = tswap32(0x20010000); /* Stack base */
< ptr[3] = tswap32(0x2000f400); /* Stack limit. */
---
> ptr[0] = tswap32(limit / 2);
> ptr[1] = tswap32(limit);
> ptr[2] = tswap32(limit); /* Stack base */
> ptr[3] = tswap32(0); /* Stack limit. */

After I made these simple changes so that the SYS_HEAPINFO command sends back proper (hard coded for my target) information to the embedded application's crt0 initialization code which sets up the system heap and stack pointer based on it my application reached to the application main without any issues.

The embedded application had to be linked with these linker description file settings for my machine:

MEMORY
{
  FLASH (rx) : ORIGIN = 0x0, LENGTH = 512K /* 128K */
  RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K /* 8K */
}

You can learn how the angel interface is working in your embedded code if you take a look into the compiler's sources here: gcc-arm-none-eabi-4_7-2013q3-20130916\src\newlib\newlib\libgloss\arm\crt0.S

After building qemu for arm (qemu-system-arm.exe), I started it with these command line parameters:

c:\MinGW\qemu-efm32\qemu-system-arm.exe -M efm32ggdk3750 -cpu cortex-m3 -nographic -monitor null -serial null -semihosting -kernel hello-CM3.elf -gdb tcp::1234,ipv4 -S

This starts qemu's gdb server listening on local host, port 1234. In eclipse I simply created an empty C/C++ makefile project, created a C/C++ Remote Application debugger profile, loaded the same elf executable as application, selected the arm-none-eabi-gdb client: c:\workspace\semihosting\tools\compiler\gcc-arm-none-eabi-4_7-2013q3-20130916\bin\arm-none-eabi-gdb.exe connecting to tcp:1234, and hit the run button...

printf output from google test running under qemu went to the gdb console:

761,504 (gdb)
784,561 56-exec-continue --thread 1
784,569 56^running
784,571 *running,thread-id="all"
784,571 (gdb)
784,586 @"[==========] Running 14 tests from 5 test cases.\n"
784,588 @"[----------] Global test environment set-up.\n"
784,590 @"[----------] 3 tests from B2C_InitTest\n"
784,592 @"[ RUN ] B2C_InitTest.ControlBlockInit\n"
784,605 @"[ OK ] B2C_InitTest.ControlBlockInit (0 ms)\n"
784,607 @"[ RUN ] B2C_InitTest.BuilderBlockInit\n"
784,609 @"[ OK ] B2C_InitTest.BuilderBlockInit (0 ms)\n"
784,610 @"[ RUN ] B2C_InitTest.BuilderBlockInitDestinationNull\n"
784,613 @"[ OK ] B2C_InitTest.BuilderBlockInitDestinationNull (0 ms)\n"
784,615 @"[----------] 3 tests from B2C_InitTest (0 ms total)\n"
784,616 @"\n"
...

File operations should also work. Passing command line arguments should also be possible to do, but I stopped my investigations when the EFM32 demo board qemu branch's changes were not accepted into main stream qemu...

Hope this helps.

Revision history for this message
franchan (francis-meyvis) said :
#6

Hello Mr Kleiber,

Thank you for your detailed answer!
The tools I use are similar and therefore I believe it could work for me as well.