1

I am using the IAR Embedded Workbench to write a C program to run on the Allwinner V831 SoC with an ARMv7 core. The SoC is on the MAIX-II Dock and I boot the system via an SD-Card.

I have a problem with the printf function. In most cases it works just fine but if I call printf in a subroutine which is called from main and the printf call is the last thing that happens in the function then the program completely crashes. Also interestingly this only happens if the subroutine is written in a separate file than the main function. I wrote two test functions:

void printf_no_fail() {
  printf("no fail 1\n");
  printf("no fail 2\n");
  printf("no fail 3\n");
  volatile int x = 3;
  volatile int y = x + 7;
}

void printf_fail() {
  printf("fail 1\n");
  printf("fail 2\n");
  printf("fail 3\n");
}

I call both these functions in main. The first one runs perfectly fine and prints all three messages. The second one breaks the program: Only the first two messages are printed and then the program crashes which I know because other code that comes in the main function is not executed.

I took a look at the assembler instructions in the list-file and there I did notice a difference.

      1          #include <stdio.h>
      2          

   \                                 In section .text, align 4, keep-with-next
      3          void printf_no_fail() {
   \                     printf_no_fail:
   \        0x0   0xE92D'5000        PUSH     {R12,LR}
      4            printf("no fail 1\n");
   \        0x4   0x....'....        ADR      R0,?_0
   \        0x8   0x....'....        BL       printf
      5            printf("no fail 2\n");
   \        0xC   0x....'....        ADR      R0,?_1
   \       0x10   0x....'....        BL       printf
      6            printf("no fail 3\n");
   \       0x14   0x....'....        ADR      R0,?_2
   \       0x18   0x....'....        BL       printf
      7            volatile int x = 3;
   \       0x1C   0xE3A0'0003        MOV      R0,#+3
   \       0x20   0xE58D'0000        STR      R0,[SP, #+0]
      8            volatile int y = x + 7;
   \       0x24   0xE59D'3000        LDR      R3,[SP, #+0]
   \       0x28   0xE283'C007        ADD      R12,R3,#+7
   \       0x2C   0xE58D'C000        STR      R12,[SP, #+0]
      9          }
   \       0x30   0xE8BD'8001        POP      {R0,PC}
     10          

   \                                 In section .text, align 4, keep-with-next
     11          void printf_fail() {
   \                     printf_fail:
   \        0x0   0xE92D'5000        PUSH     {R12,LR}
     12            printf("fail 1\n");
   \        0x4   0x....'....        ADR      R0,?_3
   \        0x8   0x....'....        BL       printf
     13            printf("fail 2\n");
   \        0xC   0x....'....        ADR      R0,?_4
   \       0x10   0x....'....        BL       printf
     14            printf("fail 3\n");
   \       0x14   0xE8BD'4002        POP      {R1,LR}
   \       0x18   0x....'....        ADR      R0,?_5
   \       0x1C   0x....'....        B        printf
     15          }

   \                                 In section .text, align 4, keep-with-next
   \                     ?_0:
   \        0x0   0x6E 0x6F          DC8 "no fail 1\012"

   \              0x20 0x66    

   \              0x61 0x69    

   \              0x6C 0x20    

   \              0x31 0x0A    

   \              0x00
   \        0xB                      DS8 1

   \                                 In section .text, align 4, keep-with-next
   \                     ?_1:
   \        0x0   0x6E 0x6F          DC8 "no fail 2\012"

   \              0x20 0x66    

   \              0x61 0x69    

   \              0x6C 0x20    

   \              0x32 0x0A    

   \              0x00
   \        0xB                      DS8 1

   \                                 In section .text, align 4, keep-with-next
   \                     ?_2:
   \        0x0   0x6E 0x6F          DC8 "no fail 3\012"

   \              0x20 0x66    

   \              0x61 0x69    

   \              0x6C 0x20    

   \              0x33 0x0A    

   \              0x00
   \        0xB                      DS8 1

   \                                 In section .text, align 4, keep-with-next
   \                     ?_3:
   \        0x0   0x66 0x61          DC8 "fail 1\012"

   \              0x69 0x6C    

   \              0x20 0x31    

   \              0x0A 0x00

   \                                 In section .text, align 4, keep-with-next
   \                     ?_4:
   \        0x0   0x66 0x61          DC8 "fail 2\012"

   \              0x69 0x6C    

   \              0x20 0x32    

   \              0x0A 0x00

   \                                 In section .text, align 4, keep-with-next
   \                     ?_5:
   \        0x0   0x66 0x61          DC8 "fail 3\012"

   \              0x69 0x6C    

   \              0x20 0x33    

   \              0x0A 0x00

In the printf_no_fail function all the printf calls are done with a Branch with Link instruction and in the printf_fail the first two are done the same way but the last call is done with a simple Branch and the link register is popped of the stack before the call which is only possible because the function ends right after the call.

I assume it is a problem with the linker since it seems to be related to the link register somehow and also only happens when the functions are written in a different file than main but I can't figure out how to fix it.

Anyone got any ideas what the problem might be?

Edit:

I compile the program and generate a raw binary as additional output. I copy the binary to the boot area of the SD card and adjust the instruction jumped to to be the first instruction of main.

At the beginning I turn on the onboard LED to see if the program is running. I have an infinite loop to see if the LED toggles which is how I know that after the printf_fail call the program doesn't continue executing as it should. The loop counting to 10 million is to delay the output because I need time to connect the board to my serial terminal. I overwrite the __write function and then use the printf from the C library.

main.c

#define PH_CONF1_REG       0x0300B100

void uart0_init(void);
void printf_no_fail(void);
void printf_fail(void);

int main(void) {
  uart0_init();
  
  *((unsigned int*)PH_CONF1_REG) &= 0xF8FFFFFF;
  *((unsigned int*)PH_CONF1_REG) |= 0x01000000;         // turn on led
  
  for(;;) {     // inifite loop to show if led is toggling
    for (volatile int i = 0; i < 10000000; i++);        // delay so I can see the output
        
      printf_no_fail();
      printf_fail();
      
      *((unsigned int*)PH_CONF1_REG) ^= 0x06000000;     // toggle led
  }
  return 0;
}

uart.c

#define UART0_BASE      0x05000000

#define UART0_RBR (UART0_BASE + 0x0)    /* receive buffer register */
#define UART0_THR (UART0_BASE + 0x0)    /* transmit holding register */

#define UART0_DLL (UART0_BASE + 0x0)    /* divisor latch low register */
#define UART0_DLH (UART0_BASE + 0x4)    /* divisor latch high register */

#define UART0_LCR (UART0_BASE + 0xc)    /* line control register */
#define UART0_LSR (UART0_BASE + 0x14)   /* line status register */

#define UART0_FCR (UART0_BASE + 0x8)    /* fifo control register */

#define GPIO_PH_CFG1    0x0300B100
#define GPIO_PH_PULL0   0x0300B118
#define UART_BGR        0x0300190C      /* uart bus gating reset register */

void uart0_init(void) {
  /* set GPIO PH9 to UART0_TX, PH10 to UART0_RX */
  *((unsigned int*)GPIO_PH_CFG1) &= 0xFFFFF00F;
  *((unsigned int*)GPIO_PH_CFG1) |= 0x00000550;
  *((unsigned int*)GPIO_PH_PULL0) |= 0x00100000;
  
  /* init UART clock */
  *((unsigned int*)UART_BGR) |= 0x00010001;

  /* select dll dlh */
  *((unsigned int*)UART0_LCR) = 0x80;
  /* set baudrate to 1.5M Baud*/
  *((unsigned int*)UART0_DLH) = 0x0;
  *((unsigned int*)UART0_DLL) = 0x1;
  /* set line control : no parity, one stop bit, 8 bit data packets*/
  *((unsigned int*)UART0_LCR) = 0x3;
  /* enable FIFOs */
  *((unsigned int*)UART0_FCR) = 0x1;
}

unsigned int __write(int handle, const unsigned char *buffer, unsigned int size) {
  unsigned int n = 0;
  if (handle == -1) return 0;
  for ( ; size > 0; size--) {
    while (!(*((unsigned int*)UART0_LSR) & (1 << 6)));
    if(*buffer == '\n') {
      *((unsigned int*)UART0_THR) = '\r';
      while (!(*((unsigned int*)UART0_LSR) & (1 << 6)));
    }
    *((unsigned int*)UART0_THR) = *buffer++;
    n++;
  }
  return n;
}
Layla
  • 11
  • 2
  • 5
    Please post [mcve] including `main` – Eugene Sh. Jun 21 '21 at 16:43
  • 4
    Q: I assume you're using the standard C library printf. So there's NO REASON either function should crash ... from what you're showing us. There's something important you're NOT showing us :( Like Eugene Sh. said - we need a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) – paulsm4 Jun 21 '21 at 16:49
  • Make two identical functions (except for their name) consisting of 3 printf only. One function in the same file as `main` and the other in another file. In `main` first call the local function then call the external function. Check that it still crash. Compare the asm of the two functions. – Support Ukraine Jun 21 '21 at 16:54
  • 2
    @Layla - The _linker_ and the _link register_ have nothing to do with each other, you know? – Armali Jun 21 '21 at 16:55
  • 2
    Welcome to StackOverflow. Other commenters have mentioned that we need some more information. I have an additional question. Please define "crash". You are running an embedded program on and ARM processor. Can you break into the debugger? Has it hit a hard fault? Stuck in infinite loop? Try to take a look at what is going on in the processor; it might give a useful hint. – Basya Jun 21 '21 at 16:57
  • The link register gets its value at runtime, from the absolute address of where the caller was executing. (PC). Linker relocations affect the destination that gets filled in for the `bl` target, but the linker can't break `bl foo` / `bx lr` call/ret other than by making `bl foo` go to the wrong place. – Peter Cordes Jun 21 '21 at 16:58
  • 2
    @Layla - I think you'll have to check whether a correct [veneer](https://developer.arm.com/documentation/ihi0042/latest#use-of-ip-by-the-linker) is inserted and addressed by the linker in the `B printf` instruction. – Armali Jun 21 '21 at 17:40
  • 1
    @Armali: I think you hit the nail on the head! References: https://stackoverflow.com/questions/64893770/, https://www.keil.com/support/man/docs/armlink/armlink_pge1406301797482.htm – paulsm4 Jun 21 '21 at 18:43
  • I don't see a solution to the problem at hand in the other question's answers. – Armali Jun 21 '21 at 18:53
  • 1
    @Armali - I have checked the binary output and the veneer is ok, the address loaded into the `pc` register corresponds with the address of the `printf` function that I get from the map file – Layla Jun 22 '21 at 10:14
  • Interesting. How can you tell that it crashes and not hangs somewhere in an endless loop? Can you interrupt the processor? – Armali Jun 22 '21 at 13:01
  • No, sadly I don't know any way to access the processor. I don't know what the processor does but I can tell the program doesn't continue running as planned because of the infinite loop in `main` that toggles the LED on the carrier board. Without the call to `printf_fail` the LED toggles, with the call inlcuded it doesn't. – Layla Jun 22 '21 at 15:08
  • It will be _very_ hard to find the error without any software or hardware debugging tools. My probably last ideas are 1. put LED toggles also in the loop in the `__write` function to see if it gets in and out of there; 2. see if you can get an emulator where the binary can be executed and inspected; 3. install an operating system on the device. – Armali Jun 22 '21 at 17:24

0 Answers0